Swift async let: The One Keyword That Makes Your App 3x Faster
Sequential async calls are killing your load times. async let runs them all at once — same syntax, way faster results.

🚀 Enter async let
Here’s the thing about regular await—it's polite. Too polite.
When you write let data = await fetchData(), your code stops and waits until it receives data. Nothing else is going to happen until that call finishes. Then it moves on to the next line.
And that’s completely fine if tasks depend on each other. But what if they don’t?
let store = await fetchStore() // waits 2 seconds
let products = await fetchProducts() // waits 3 seconds
let shelves = await fetchShelves() // waits 2 seconds
// total: 7 secondsThose three calls don’t need each other. Store data doesn’t depend on products. Product don’t need shelves. They could all run at the same time.
Enter async let.
async let store = fetchStore() // starts immediately
async let products = fetchProducts() // starts immediately
async let shelves = fetchShelves() // starts immediately
let storeData = await store // wait for all three
let productsData = await products
let shelvesDat = await shelves
// total: 3 seconds (the longest one)Same three calls. 3 seconds instead of 10. That’s the magic — async let kicks off the task but doesn't wait for it. Instead, it waits later if required, and only when you actually need the result.
Think of it like ordering at a food court. If you have to go sequentually, you order pizza, wait for it, then order a drink, wait for it, then order fries. On the other hand, you order all three at once, then wait at the pickup counter for everything together.
The syntax is dead simple:
async let x = someAsyncFunction()starts the taskawait xwaits for the result when you need it
That’s it. One keyword swap and your parallel execution is done.
No Task Groups. No manual continuation management. Just slap async before let and you're running in parallel.
**Swift For-Await Loops: Handle Real-Time Data Without the Chaos** _Network streams, notifications, sensor data — for-await loops make async sequences feel like normal Swift code…_swift-pal.comhttps://swift-pal.com/swift-for-await-loops-handle-real-time-data-without-the-chaos-e1f2d8d98f30
⏱️ Real Performance Comparison
Let’s prove this with actual code you can run. We’ll simulate three API calls, each taking different amounts of time.
import Foundation
// fake API calls with delays
func fetchUser() async -> String {
try? await Task.sleep(nanoseconds: 2_000_000_000) // 2 sec
return "User: Alice"
}
func fetchPosts() async -> String {
try? await Task.sleep(nanoseconds: 3_000_000_000) // 3 sec
return "Posts: 42"
}
func fetchFriends() async -> String {
try? await Task.sleep(nanoseconds: 2_000_000_000) // 2 sec
return "Friends: 150"
}
// Sequential version (slow)
func loadDataSequential() async {
let start = Date()
let user = await fetchUser()
let posts = await fetchPosts()
let friends = await fetchFriends()
let elapsed = Date().timeIntervalSince(start)
print("\(user), \(posts), \(friends)")
print("Sequential time: \(elapsed) seconds")
}
// Parallel version (fast)
func loadDataParallel() async {
let start = Date()
async let user = fetchUser()
async let posts = fetchPosts()
async let friends = fetchFriends()
let userData = await user
let postsData = await posts
let friendsData = await friends
let elapsed = Date().timeIntervalSince(start)
print("\(userData), \(postsData), \(friendsData)")
print("Parallel time: \(elapsed) seconds")
}
// run both
Task {
await loadDataSequential()
print("---")
await loadDataParallel()
}Run this and you’ll see:
User: Alice, Posts: 42, Friends: 150
Sequential time: 7.2952070236206055 seconds
---
User: Alice, Posts: 42, Friends: 150
Parallel time: 3.0890719890594482 secondsThe parallel version finishes in the time of the slowest task (3 seconds for posts). The sequential version adds them all up (2+3+2 = 7 seconds).
That’s not a micro-optimization. That’s user-noticeable performance.
Now here’s something cool — you can mix and match. If one task depends on another, use regular await. For independent stuff, use async let.
let userId = await fetchUserId() // need this first
async let profile = fetchProfile(userId) // can start now
async let settings = fetchSettings(userId) // can start now
let userData = await profile
let userSettings = await settingsUser ID has to come first. But once you have it, profile and settings can run together.
🛠️ Practical Use Cases
Okay, so when does this actually matter in real apps?
• Dashboard Loading
Loading a user dashboard — you need user info, notifications, recent activity, maybe weather data. None of these depend on each other.
func loadDashboard() async -> Dashboard {
async let profile = api.fetchProfile()
async let notifications = api.fetchNotifications()
async let activity = api.fetchRecentActivity()
async let weather = api.fetchWeather()
return Dashboard(
profile: await profile,
notifications: await notifications,
activity: await activity,
weather: await weather
)
}Four API calls in parallel instead of sequence. If each takes 1 second, you just saved 3 seconds of loading time.
• Batch Image Processing
Downloading multiple images for a gallery:
func loadGallery(urls: [URL]) async -> [UIImage?] {
// don't do this - sequential
// var images = [UIImage]()
// for url in urls {
// let image = await downloadImage(from: url)
// images.append(image)
// }
// do this - parallel (kind of)
await withTaskGroup(of: UIImage?.self) { group in
for url in urls {
group.addTask {
await downloadImage(from: url)
}
}
var images = [UIImage?]()
for await image in group {
images.append(image)
}
return images
}
}Wait, that’s not async let — that’s Task Groups. Yes, for dynamic collections you need Task Groups. async let works best when you know exactly how many tasks you have at compile time.
Let me show you the async let version for a fixed set:
func loadProfileImages() async -> (UIImage?, UIImage?, UIImage?) {
async let avatar = downloadImage(from: avatarURL)
async let cover = downloadImage(from: coverURL)
async let thumbnail = downloadImage(from: thumbnailURL)
return (await avatar, await cover, await thumbnail)
}Three specific images = async let
Unknown number of images = Task Groups
• Multi-Source Data Aggregation
Fetching data from different services to build one view:
func loadProductDetails(id: String) async -> ProductView {
async let product = database.fetchProduct(id)
async let reviews = reviewService.fetchReviews(id)
async let inventory = inventoryAPI.checkStock(id)
async let recommendations = mlService.getRecommendations(id)
return ProductView(
product: await product,
reviews: await reviews,
inStock: await inventory,
similar: await recommendations
)
}Each service is independent. Run them all at once.
One gotcha though — if any of these throw errors, you need to handle them individually:
async let product = database.fetchProduct(id)
async let reviews = reviewService.fetchReviews(id)
do {
let productData = try await product
let reviewData = try await reviews
// use both
} catch {
// handle errors
}All async let tasks start immediately, even inside the do block. The try await is where you actually wait for results.
🎯 async let vs Task Groups
So when do you use async let vs Task Groups? Both do parallel execution, but they fit different scenarios.
Use async let when:
- You know exactly how many tasks at compile time
- Tasks are different types or unrelated
- You want simple, readable code
- Usually 2–5 tasks
async let user = fetchUser()
async let posts = fetchPosts()
async let settings = fetchSettings()Clean. Obvious. Each task has a name.
Use Task Groups when:
- Number of tasks is dynamic (loops, arrays)
- All tasks return the same type
- You need to process results as they complete
- Could be hundreds of tasks
await withTaskGroup(of: String.self) { group in
for id in userIDs {
group.addTask { await fetchUser(id) }
}
for await result in group {
process(result) // handle as each finishes
}
}Task Groups give you more control — you can cancel individual tasks, handle results as they arrive, set priorities. But they’re more verbose.
Here’s the decision tree:
- Fixed number of different tasks? → async let
- Loop over a collection? → Task Group
- Need fine-grained control? → Task Group
- Want simple parallel calls? → async let
You can even mix them:
async let metadata = fetchMetadata()
let items = await withTaskGroup(of: Item.self) { group in
for id in ids {
group.addTask { await fetchItem(id) }
}
// collect results
}
let meta = await metadata
return (meta, items)One fixed task (metadata) with async let, dynamic tasks (items) with Task Group.
Actually — one more thing about async let. The tasks are child tasks of the current task. If the parent gets cancelled, all async let tasks cancel too. That’s usually what you want, but worth knowing.
let task = Task {
async let a = longRunningTask()
async let b = anotherLongTask()
let resultA = await a
let resultB = await b
}
task.cancel() // cancels both a and bAutomatic cleanup. Nice.
💡 Wrap-Up
async let = parallel execution without the ceremony.
Use it when:
- Multiple independent async calls
- Fixed number of tasks (known at compile time)
- Want clean, readable parallel code
- Each task has a clear name/purpose
Skip it when:
- Tasks depend on each other (use regular await)
- Dynamic number of tasks (use Task Group)
- Need fine control over task lifecycle
The syntax is just:
async let x = asyncFunction() // starts now
let result = await x // wait when neededPerformance win? Massive. Instead of adding up all task times, you wait for the longest one. Three 2-second calls? 2 seconds total, not 6.
One gotcha: all async let tasks start immediately when you declare them, not when you await them. So don’t declare them if you’re not sure you need them.
async let data = expensiveCall() // already running!
if someCondition {
let result = await data // only use it conditionally
}
// task runs even if condition is false - wastefulBetter to check conditions first, then declare async let tasks you’ll definitely use.
📺 Want to see this with real examples and live coding? Check out the complete tutorial on Swift Pal: https://youtube.com/@swift-pal
That’s async let. One keyword. Way faster apps. Finally running things in parallel without the Task Group boilerplate.
🎉 Enjoyed this article? Your support means the world to me!
💬 Drop a comment below! I love hearing about your experiences and answering questions
🎬 Subscribe on Youtube and become early subscribers of my channel: https://www.youtube.com/@swift-pal
💼 Let’s connect on LinkedIn for more professional insights: https://www.linkedin.com/in/karan-pal
Happy coding! 🚀
New articles, straight to your inbox.
No spam, no filler — just new writing on iOS, the web, and AI when it ships. Unsubscribe anytime.
Keep reading
Why I'm Rebuilding My Blog From Scratch (and Leaving Medium)
After years of publishing on someone else's platform, I'm moving my writing to a home I actually own. Here's the reasoning, and what I'm building instead.
ReadData Is the Model: The Most Ignored Part of AI
A beginner-friendly guide to why data quality beats model hype.
Read