Understanding Task and Task.detached in Swift concurrency
Learn the what the differences are between Task and Task.detached in Swift.
02 Nov 2025 · 6 min read
When we explored structured and unstructured tasks, we saw a small example of using Task {} vs. Task.detached {}.
In this article, we'll go a bit deeper and look at how the two differ, and when it makes sense to use each.

Staying in context with Task
When we create a regular Task, it inherits context from where it's started.
That means:
- If we’re inside an actor, the new task runs on that actor.
- If the parent task has a certain priority or is cancelled, that state is inherited.
For example:
actor DataService {func refresh() {Task {await updateCache()}}}
Here, updateCache() runs inside the actor. We don't need to think about isolation or thread safety, Swift takes care of that.
Creating an independent task with Task.detached
Task.detached starts a completely independent task. It doesn't inherit any context: not actor isolation, not priority, not cancellation.
In some cases, we can offload work from an actor so that it stays responsive. Actors process one piece of work at a time, so if we want to perform longer operations that don't depend on the actor's state, we can run them outside the actor's executor:
actor UserService {private let secureStore: SecureStorefunc clear() {Task.detached { [secureStore] inawait secureStore.clearTokens()}}}
Here, the actor becomes available immediately after starting the detached task.
However, this only works if secureStore is safe to call from outside the actor, for example, if they are actors themselves or thread-safe.
When detaching doesn't help
The example above works because the detached task doesn't touch the actor's state. But if we try to access it, we lose the benefit:
actor DataService {func refresh() {Task.detached { [weak self] inguard let self else { return }await self.updateCache()}}private func updateCache() async { ... }}
This compiles, but it's effectively the same as using a regular Task. The detached task runs independently, then immediately hops back into the actor to do its work. So there's no real advantage here, we could have used a regular Task instead.
Also, it's a common misconception that Task.detached is necessary to make a task "keep running". That's not the case. Once a normal task is created outside structured concurrency, it already runs independently. A detached task doesn’t change that behavior. For example:
class DataService {func startWork() {Task {await doLongRunningWork()}}}
This task will continue until it completes or is cancelled explicitly. Replacing it with Task.detached changes nothing in practice.
So if our goal is simply to start background work that finishes on its own, a normal Task is usually enough.



Newsletter
Like to support my work?
Say hi
Related tags
Articles with related topics
Latest articles and tips



