Answer
The core idea
These three APIs solve different problems.
Task and Task.detached create unstructured tasks that are independent of the current function's lifetime. A task group creates structured child tasks whose lifetime is tied to their parent task.
There is no DetachedTask type. Detached work is created with Task.detached { ... }.
The short interview answer is: Task inherits the caller's execution context, Task.detached starts with an independent context, and a task group is the way to fan out child work under structured concurrency.
1. Task vs Task.detached
Task is the usual way to start asynchronous work from synchronous code, such as a button action or UIKit callback:
Task {
let result = await fetchData()
print(result)
}A Task inherits useful execution context from where it is created, including:
- priority
- task-local values
- actor isolation (when created from an actor-isolated context)
This makes it the default choice for almost all application code.
Task.detached starts an independent task that does not inherit any of that context:
Task.detached {
let result = await fetchData()
print(result)
}Detached tasks do not inherit:
- priority
- task-local values
- actor isolation
Use Task.detached sparingly, for example when you intentionally want work that should execute independently of the caller's actor or task hierarchy. In most cases, prefer Task.
Neither Task nor Task.detached is cancelled simply because the function that created it returns. Since both are unstructured tasks, managing their lifetime and cancellation is your responsibility unless another part of your program explicitly cancels them.
2. Task groups for structured fan-out
Task groups solve a different problem. Every child task belongs to the group, and the parent task cannot leave the group until all child tasks have completed.
func fetchAllTitles(ids: [Int]) async -> [String] {
await withTaskGroup(of: String.self) { group in
for id in ids {
group.addTask {
await fetchTitle(id: id)
}
}
var titles: [String] = []
for await title in group {
titles.append(title)
}
return titles
}
}Task groups automatically provide the benefits of structured concurrency:
- child tasks cannot outlive the group
- parent and child lifetimes stay aligned
- cancellation propagates through the group
- errors propagate when using
withThrowingTaskGroup
Use a task group when the number of child tasks is dynamic or when the parent should treat all of the work as a single operation.
If you only have a small, fixed number of concurrent child tasks, async let is usually simpler.
3. Why it matters
Choosing the wrong abstraction often leads to work continuing after a screen disappears, unexpected actor hops, or manual coordination that structured concurrency already provides.
Use structured concurrency whenever possible. Reach for unstructured tasks only at the boundaries of your application, such as starting asynchronous work from synchronous APIs or integrating with existing frameworks.
Interview angle
Start with the key distinction: Task and Task.detached create unstructured tasks, while task groups create structured child tasks.
Then explain the difference between Task and Task.detached: Task inherits execution context (priority, task-local values, and actor isolation), while Task.detached starts independently.
Finish with a task-group example using addTask and for await, and the rule of thumb:
- Use
Taskby default. - Use task groups for concurrent child work that belongs together.
- Use
Task.detachedonly when you intentionally need work that should not inherit the caller's execution context.
The Swift book section on Tasks and task groups is the canonical reference.