async let vs TaskGroup

Concurrency Mid-level Swift 5.5+ iOS 15+ Reviewed By Alok Singh

Pick the fix

You need avatars for ids: [UUID] (length known only at runtime), fetched in parallel. Which code?

Options
Hint 1 of 2

Is the number of calls known when you write the code?

Hint 2 of 2

Which API lets you add children in a loop?

Choose an answer, then open Show answer below to check it.

Show answer

Answer: B withTaskGroup(of: (UUID, UIImage).self) { group in for id in ids { group.addTask { (id, await fetch(id)) } } ... }

Correct. A group handles a runtime number of children and keeps them structured.

Why not the others?
  • A for id in ids { async let a = fetch(id); images.append(await a) }. Each async let belongs to one loop iteration and is awaited before the next starts, so this runs one at a time.
  • C let tasks = ids.map { id in Task { await fetch(id) } }. It runs in parallel, but the tasks are unstructured: leaving the screen won't cancel them.
  • D let images = await ids.map { await fetch($0) }. map takes a synchronous closure, so you can't await inside it. This doesn't compile.
Shared 0–3 s axis: async let inside a loop runs three fetches one after another (3 s); a TaskGroup runs them together (1 s).0 s1 s2 s3 sasync let in a loopfetch 1fetch 2fetch 33 sTaskGroupfetch 1fetch 2fetch 31 s
Shared 0–3 s axis: async let inside a loop runs three fetches one after another (3 s); a TaskGroup runs them together (1 s).
Text version
Timeline as text, elapsed time t in seconds
VersionWhat runs whenFinishes at
async let in a loop
  • fetch 1 from t = 0.0 s to t = 1.0 s
  • fetch 2 from t = 1.0 s to t = 2.0 s
  • fetch 3 from t = 2.0 s to t = 3.0 s
t = 3.0 s
TaskGroup
  • fetch 1 from t = 0.0 s to t = 1.0 s
  • fetch 2 from t = 0.0 s to t = 1.0 s
  • fetch 3 from t = 0.0 s to t = 1.0 s
t = 1.0 s

30-second answer

Use async let for a fixed, small number of independent calls you know when writing the code. Use a TaskGroup when the number of child tasks depends on runtime data, such as one fetch per ID. Both are structured: child tasks finish or are cancelled before the scope exits.

Practise saying it · 45 s. No recording, just a timer

Explain async let versus TaskGroup to an interviewer.

Say your answer out loud, then check which of these points you covered:

  • async let: fixed number of calls
  • TaskGroup: runtime number of calls
  • Both are structured and cancel children
  • Mentions limiting concurrency

Deep dive

async let starts a child task immediately; await later only collects the result.

withTaskGroup / withThrowingTaskGroup let you addTask in a loop and consume results as they finish.

To limit concurrency, add the first N tasks, then add one more each time group.next() returns.

If one child throws in a throwing group and you rethrow, the remaining children are cancelled.

Group with a concurrency limit Type-checked · Swift 6.4
func thumbnails(for ids: [UUID], limit: Int = 4) async -> [UUID: UIImage] {
    await withTaskGroup(of: (UUID, UIImage?).self) { group in
        var iterator = ids.makeIterator()
        for _ in 0..<limit {
            guard let id = iterator.next() else { break }
            group.addTask { (id, await fetchThumbnail(id)) }
        }
        var result: [UUID: UIImage] = [:]
        while let (id, image) = await group.next() {
            result[id] = image
            if let next = iterator.next() {
                group.addTask { (next, await fetchThumbnail(next)) }
            }
        }
        return result
    }
}

Common mistakes

  • Declaring async let inside a loop and expecting parallelism.
  • Using unstructured Task {} per item and losing cancellation.
  • Assuming results come back in the order tasks were added.

Senior take & follow-ups

Seniors mention back-pressure: an unbounded group of 1,000 downloads is a bug even though it compiles.

Follow-up questions

Think of your answer first, then open the hint.

  1. How do you keep results in input order?

    Show hint

    Return (index, value) pairs and place them in an array.

  2. How would you cap a group at 4 concurrent downloads?

    Show hint

    Seed 4 tasks, then add one per next().

Suggest a fix if something here is wrong or out of date.