Concurrency
Swift has two eras of concurrency, and an interview asks about both. The old one is imperative: you hand-place work on GCD queues (DispatchQueue), choose sync or async and a QoS class, and protect shared mutable state with a serial queue or a lock. The new one is declarative: async/await and structured concurrency describe what depends on what, and the compiler and runtime manage the threads for you. On top of it, actors serialize access to their own state, and Sendable plus strict concurrency checking catch data races before you ever run.
Both eras share one through-line — protecting shared mutable state and behaving predictably around threads. That is exactly where the classic interview traps hide: await does not block a thread, it suspends a task; DispatchQueue.main.sync from the main thread is an instant deadlock; an actor is reentrant, so its invariant can break across an await; and a data race and a race condition are not the same thing. We walk each layer in order.
Topic map
- async/await — structured concurrency, suspension points,
Taskand task groups,async let, cooperative cancellation, andcontinuations to bridge legacy code. - GCD — queues (serial and concurrent, the main queue),
syncversusasync,QoSclasses, the classic deadlock,DispatchGroupand barriers, thread explosion. - Actors & data races — actors serialize access to their state, reentrancy,
@MainActor,Sendableand compile-time checks,nonisolated, and the difference between a data race and a race condition.
Common traps
| Mistake | Consequence |
|---|---|
Thinking await blocks the thread | Wrong model — the task suspends and the thread is free for other work |
DispatchQueue.main.sync from the main thread | Instant deadlock: the thread waits on itself |
Updating UI off the main thread / @MainActor | Rare crashes that the Main Thread Checker flags |
Assuming an actor protects an invariant across await | Reentrancy: the state may have changed during the suspension |
| Treating a data race and a race condition as synonyms | Different bugs — one is caught by the compiler, the other is not |
Throttling on DispatchSemaphore with a blocking wait | Thread explosion and a starved pool |
Interview relevance
Concurrency is asked as a test of your mental model: do you understand what happens to threads and shared state, or are you reciting recipes? A candidate who says "await is a suspension point: the task is lifted off the thread, the thread picks up other work instead of idling" immediately gets ahead of one who thinks await "blocks the thread until the result arrives".
Typical checks:
- The difference between
syncandasync, and whymain.syncfrom the main thread deadlocks. - Why
awaitdoes not block a thread and what a suspension point is. - How actors remove data races and why reentrancy still demands care.
- The difference between a data race and a race condition, and which the compiler catches.
Common wrong answer: "async/await is just new syntax on top of GCD, and await blocks the thread until the result comes back". In reality await suspends the task and frees the thread, structured concurrency adds cancellation and a parent-child relationship, and actors provide what GCD never did — protection from data races at compile time.