Advanced Concurrency: async let, TaskGroup & Actors

Go beyond a single await . Run work in parallel with async let and withTaskGroup , protect shared state with actors , pin UI work to @MainActor , and let the compiler guard you with Sendable .

Learn Advanced Concurrency: async let, TaskGroup & Actors in our free Swift course — a beginner-friendly interactive lesson with worked examples, a practice…

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

1️⃣ async let — Parallel Work

Each async let launches a child task right away. The tasks run side by side; you join the results with await only when you actually use them.

2️⃣ withTaskGroup — Many Tasks

When the number of tasks isn't fixed, a task group lets you addTask in a loop and gather results with for await as each finishes.

3️⃣ Actors — Safe Shared State

An actor serializes access to its state so only one task touches it at a time — no data races. Code inside the actor is synchronous; code outside must await . Use @MainActor to pin UI work to the main thread, and Sendable to mark types safe to share.

Now you try. Add the keyword cross-actor calls need.

📋 Quick Reference

Fetch two simulated sizes in parallel and sum them, then try the task-group version. Compare with the comments.

Practice quiz

What does 'async let' enable?

  • Sequential awaiting
  • Starting child tasks that run in parallel, awaited later
  • Cancelling tasks
  • Locking a property

Answer: Starting child tasks that run in parallel, awaited later. async let starts a child task immediately and lets you await its result later, enabling parallelism.

When do you await an async let binding?

  • Never
  • Before declaring it
  • At the point you actually use its value
  • Only inside an actor

Answer: At the point you actually use its value. You await the async let when you need its result, allowing the work to run concurrently until then.

What is withTaskGroup used for?

  • Running a dynamic number of child tasks concurrently
  • Defining a struct
  • Storing data
  • Animating views

Answer: Running a dynamic number of child tasks concurrently. A task group spawns a variable number of concurrent child tasks and collects their results.

What problem do actors solve?

  • Slow networking
  • Parsing JSON
  • Drawing UI
  • Protecting mutable state from data races

Answer: Protecting mutable state from data races. Actors serialize access to their mutable state, preventing concurrent data races.

How do you usually access an actor's properties from outside?

  • Directly and synchronously
  • With await, because access is asynchronous
  • Through a global lock
  • You cannot access them

Answer: With await, because access is asynchronous. Cross-actor access is asynchronous, so it requires await.

What does @MainActor guarantee for a function or type?

  • It runs on a background thread
  • It cannot be awaited
  • Its code runs on the main thread
  • It disables concurrency

Answer: Its code runs on the main thread. @MainActor isolates code to the main thread, ideal for UI updates.

What does the Sendable protocol indicate?

  • A type can be encoded to JSON
  • A type is a view
  • A type is an actor
  • A type is safe to pass across concurrency boundaries

Answer: A type is safe to pass across concurrency boundaries. Sendable marks types that can be safely shared between concurrent tasks.

Inside its own methods, an actor accesses its state...

  • Synchronously, without await
  • With await every time
  • Only via closures
  • Never

Answer: Synchronously, without await. Code already running on the actor accesses its own isolated state synchronously.

Child tasks created with async let are cancelled if...

  • They never start
  • The enclosing scope exits before they're awaited
  • You call await
  • The actor is deallocated

Answer: The enclosing scope exits before they're awaited. Structured child tasks are cancelled when their scope ends without awaiting them.

A key benefit of structured concurrency is...

  • Manual thread management
  • Avoiding async entirely
  • Child task lifetimes are tied to a scope, simplifying cleanup
  • Removing the need for actors

Answer: Child task lifetimes are tied to a scope, simplifying cleanup. Structured concurrency ties task lifetimes to scopes, making cancellation and cleanup predictable.