Home / async/await interview questions

Swift concurrency

async/await interview questions

async/await is the foundation of modern Swift concurrency and a frequent live-coding task. Here is what interviewers ask and how to reason about it.

What gets asked

A very common task is converting a completion-handler API into an async function with withCheckedThrowingContinuation. Interviewers also ask you to run several async calls concurrently with async let or a task group, combine results, and handle errors any child task might throw. Conceptual questions cover how await suspends without blocking a thread.

How to reason about it

Emphasize that await is a suspension point, not a blocking call: the thread is freed while the awaited work runs. Use async let for a fixed set of parallel calls and a task group for a dynamic count. Treat cancellation as cooperative, checking Task.isCancelled or letting CancellationError propagate rather than assuming work stops on its own.

What actually happens when you write await?

The spoken answer: "await marks a suspension point. If the awaited work is not ready, the function suspends, its state is saved in a continuation, and the thread goes back to the pool to run other work. When the result arrives, the function resumes, possibly on a different thread. Nothing blocks." Two breaths, and it covers the part most candidates get wrong.

The trap: "So the code after await runs on the same thread it started on?" Saying yes fails, because resumption can land on any thread in the cooperative pool unless an actor pins it. The recovery line: "Not guaranteed; the executor decides, which is why isolation comes from actors, not from threads." The senior differentiator is stating the rule that follows: never hold a lock across an await, because the resuming thread may not be the one that acquired it. That single sentence tells me you have debugged real concurrency code.

Convert this completion handler API to async/await

This is the most common live coding task in the concurrency round. The spoken framing: "I wrap the call in withCheckedThrowingContinuation, resume with the value on success and the error on failure, and I make sure the continuation resumes exactly once on every path."

The trap is a callback that can complete twice, or an API where both the result and the error are optional. Candidates who resume in each branch without thinking walk into a double resume crash or a path that never resumes and hangs forever. The recovery line: "The contract is exactly once; I would audit every exit path of the closure, including the case where both values are nil, and decide what error that maps to." The senior differentiator is knowing why checked continuations exist: they detect double and missed resumes at runtime, and you trade to unsafe continuations only after profiling shows the check matters, which it rarely does.

What is an actor, and what does it protect you from?

The spoken answer: "An actor serializes access to its mutable state. Calls from outside are async and run one at a time on the actor, so data races on that state are gone by construction. It replaces the lock and queue boilerplate we used to write by hand."

The trap: "So code on an actor is free of race conditions?" Yes walks straight into it. Actors prevent data races, not logic races: every await inside an actor method is a reentrancy point where another call can interleave and change state under you. The recovery line: "Data races, yes; interleaving, no. I re-check invariants after every await inside an actor." The senior differentiator is mentioning @MainActor as the same mechanism applied to UI state, and Sendable as the compiler enforced boundary that decides what may cross into an actor at all.

Structured concurrency: why interviewers ask about task groups

The spoken answer for the classic "fetch these in parallel" task: "For a fixed set, async let. For a dynamic set, withThrowingTaskGroup. Either way the tasks are children: if one throws, the rest are cancelled, and the scope does not exit until every child finishes. That is the structured part."

The trap: the interviewer asks what happens to the other downloads when one fails, or what Task.detached would change. Reaching for detached tasks fails the question, because detached work escapes the cancellation tree and outlives the scope, which is exactly the leak structured concurrency exists to prevent. The recovery line: "Cancellation is cooperative; the failed group cancels its children, and each child must check for cancellation to stop early." The senior differentiator is saying when unstructured tasks are legitimate, for example fire and forget analytics from a synchronous context, and that you still name the tradeoff out loud.

Go deeper than a summary

Share Your Screen works topics like this through in full: 82 solved Swift problems with tests, 9 case studies (one real round, eight composites), and 7 annotated mock interviews, so you rehearse the live round instead of just reading about it.

Get the book

Secure checkout on Gumroad. Instant PDF and EPUB download. Lifetime updates.

Frequently asked

How do you convert a completion handler to async/await?

Wrap it with withCheckedContinuation or withCheckedThrowingContinuation, resuming the continuation exactly once inside the completion closure.

async let or task group?

async let suits a fixed, known number of parallel calls; a task group suits a dynamic number of child tasks added and awaited in a loop.