Concurrency: async/await
Write asynchronous code that reads like ordinary top-to-bottom code. Learn async / await , launch work with Task , run things in parallel with async let , and keep shared state safe with actors and the @MainActor .
Learn Concurrency: async/await in our free Swift course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick…
Part of the free Swift course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What You'll Learn in This Lesson
- • What async and await mean (suspend and resume)
- • Calling async code from sync code with Task { }
- • Sequential awaits vs concurrent async let
- • Running independent work in parallel for speed
- • Protecting shared mutable state with actors
- • Running UI code on the main thread with @MainActor
1️⃣ async / await Basics
An async function can suspend ; await marks where it pauses. From ordinary code, wrap async calls in a Task { } to create an async context.
2️⃣ Sequential awaits
Plain await calls run one after another — each waits for the previous to finish. That's correct when later work depends on earlier results.
3️⃣ Concurrency with async let
When operations are independent , async let starts them all at once so they overlap. You await the results later — total time is roughly the longest single task, not the sum.
4️⃣ Actors and @MainActor
An actor serializes access to its mutable state, preventing data races. Reaching into an actor from outside is asynchronous, so it needs await . The special @MainActor pins code to the main thread — essential for UI updates.
Your turn. Fill in the async keyword, the suspend keyword, and the Task type.
Pro Tips
- 💡 Use async let whenever requests don't depend on each other — it's free speed.
- 💡 Update UI on the main actor. Mark UI code @MainActor or hop with await MainActor.run { } .
- 💡 Prefer actors over manual locks for shared mutable state — they're race-free by design.
- 💡 One await per suspension. Add try too when the call can throw: try await .
Common Errors (and the fix)
- "'async' call in a function that does not support concurrency": you called async code from sync code. Wrap it in Task { } or mark the caller async .
- "Expression is 'async' but is not marked with 'await'": add await before the call.
- Awaiting calls one-by-one when they're independent: switch to async let to run them in parallel.
- UI updated off the main thread: wrap it with @MainActor / await MainActor.run { } .
📋 Quick Reference
Task
Code
Effect
Define async
func f() async { }
may suspend
Call it
await f()
suspend point
From sync code
Task { await f() }
async context
Parallel
async let x = f()
runs concurrently
Safe state
actor C { … }
no data races
Main thread
@MainActor
UI-safe
Frequently Asked Questions
Mini-Challenge: Parallel Loaders
No blanks this time — just a brief and an outline. Load three things concurrently with async let and print them together.
Practice quiz
What does marking a function 'async' mean?
- It runs on the GPU
- It always runs in parallel
- It can suspend and resume — pausing without blocking the thread
- It cannot fail
Answer: It can suspend and resume — pausing without blocking the thread. 'async' marks a function that may suspend at await points, freeing the thread while it waits.
What does the 'await' keyword do?
- Marks a point where the function may suspend until the async result is ready
- Blocks the thread until done
- Starts a new thread
- Catches an error
Answer: Marks a point where the function may suspend until the async result is ready. 'await' marks a potential suspension point where execution can pause and later resume.
How do you call an async function from non-async (synchronous) code?
- Just call it directly
- Use a for loop
- It is impossible
- Wrap it in a Task { }
Answer: Wrap it in a Task { }. A Task { } creates an async context so you can call and await async functions from sync code.
What is 'async let' used for?
- Declaring a constant
- Starting child tasks that run concurrently, awaited later
- Looping asynchronously
- Defining an actor
Answer: Starting child tasks that run concurrently, awaited later. 'async let' kicks off concurrent work immediately and you await the results when you need them.
Compared to sequential awaits, what does 'async let' improve?
- It lets independent operations run in parallel, reducing total wait time
- Memory usage only
- Code safety only
- Nothing
Answer: It lets independent operations run in parallel, reducing total wait time. Independent async let bindings run concurrently instead of one-after-another, so the total time is roughly the longest one, not the sum.
What problem do actors primarily solve?
- Slow networking
- JSON decoding
- Protecting mutable state from data races by serializing access
- UI layout
Answer: Protecting mutable state from data races by serializing access. An actor isolates its mutable state so only one task touches it at a time, preventing data races.
Accessing an actor's property or method from outside the actor usually requires what?
- A force-unwrap
- await, because the access may suspend
- A guard statement
- Nothing special
Answer: await, because the access may suspend. Cross-actor access is asynchronous, so you must await it.
What does the @MainActor attribute guarantee?
- The code runs on a background thread
- The code never suspends
- The code is private
- The code runs on the main thread/actor — required for UI updates
Answer: The code runs on the main thread/actor — required for UI updates. @MainActor ensures the annotated code runs on the main actor, which is where UI work must happen.
Inside an async function, how do you await two independent calls so they overlap?
- await a(); await b()
- async let x = a(); async let y = b(); let r = await (x, y)
- Task { a() }; Task { b() }
- guard let
Answer: async let x = a(); async let y = b(); let r = await (x, y). async let starts both concurrently; awaiting the tuple lets them overlap rather than run sequentially.
Why is async/await preferred over nested completion-handler callbacks?
- It is the only option
- It is faster on the CPU
- It reads top-to-bottom like synchronous code, avoiding callback nesting
- It removes the need for error handling
Answer: It reads top-to-bottom like synchronous code, avoiding callback nesting. async/await flattens asynchronous logic into straight-line, readable code instead of pyramids of callbacks.
Continue this course
- Previous: Memory Management & ARC
- Next: Codable & JSON