async/await, the foundation
Unit 2 · Strict Concurrency. This unit is deliberately early and long: data-race safety is the #1 thing 2026 iOS interviews probe, so Segue treats it as a core module, not an appendix.
Modern Swift concurrency starts with two keywords. async marks a function that may
suspend; await marks the point where a caller waits for it. Together they let you
write asynchronous code that reads top-to-bottom like ordinary code — no nested callbacks,
no completion handlers.
Declaring and calling
func loadUser(id: Int) async -> String { // …some awaited work… "user-\(id)" } // Calling it — the await is required and visible: let name = await loadUser(id: 42)
await is a suspension point: the function may pause there, let other work run, then
resume with the result. Crucially it does not block a thread the way a synchronous wait
would — the thread is freed to do other work while you're suspended. That's why thousands of
async tasks can share a small thread pool.
await does not mean "this is slow" — it means "state may change while I'm paused here."
After an await, don't assume anything you read before it is still true; re-check what
matters. This is the mental discipline the visible suspension points are there to support.
Asynchrony is contagious (and that's good)
A function that calls an async function must itself be async. The colour spreads up the
call chain until it reaches a boundary that can start async work: a Task, a SwiftUI
.task modifier, or an @main entry point.
// Bridging from a synchronous context (e.g. a button action): Button("Load") { Task { let name = await loadUser(id: 1) print(name) } }
Task { … } creates a new top-level task that runs the async work. In SwiftUI you'll more
often use .task { } on a view, which also ties the work's lifetime to the view's.
Swift's async/await maps closely to Kotlin suspend functions. Task { } is the rough
analogue of launching in a coroutine scope, and structured concurrency (next lesson) mirrors
coroutineScope { }. The big difference lands in Unit 2.5: Swift's Sendable checking is
enforced by the compiler, not left to discipline.
Your turn
Await an async call and shape its result.
Knowledge check
Q: Does await block the current thread?
No. It suspends the task and frees the thread for other work; the task resumes later.
This is what lets async code scale far past the number of OS threads.
Q: You call an async function from a plain synchronous function and it won't compile.
What's the fix?
Start a Task { } (or make the caller async and push the boundary up). Something has to
create the asynchronous context.