How does Swift enforce (or not enforce) thread safety?
The core mechanism: compile-time isolation checking
Swift’s strict concurrency checking works by tracking isolation domains at compile time and requiring proof that data crossing between them is safe. It’s static analysis, not a runtime lock/mutex system.
1. Actors serialize access to their own mutable state — synchronous access to actor-isolated state from outside the actor is a compile error.
2. Sendable gates what can cross an isolation boundary — any value passed between isolation domains must conform to Sendable.
class Counter { var value = 0 } // not Sendable — no synchronization
func launch(_ counter: Counter) {
Task {
counter.value += 1 // ❌ error: Counter is not Sendable
}
}
3. sending as the finer-grained escape valve — lets a specific non-Sendable value cross a boundary once, if provably in a disconnected region.
What this actually prevents
Genuine low-level data races — two threads concurrently reading/writing the same memory without synchronization. If Swift 6 strict mode compiles clean, you provably don’t have this class of bug.
What it does NOT prevent
1. Reentrancy bugs — an actor’s isolation guarantees “only one task runs isolated code at a time,” but suspension points (await) are reentrancy windows.
2. Escape hatches that opt out entirely
@unchecked Sendable— asserts safety yourself, unverified by the compiler.nonisolated(unsafe)— bypasses actor isolation checks at the property level.
3. C/Objective-C interop and unsafe pointers — no Swift-visible isolation information at all.
4. Priority inversion, deadlocks, and performance issues — strict concurrency checking is about correctness, not liveness.
5. Logic errors around ordering and state machines generally.
The precise claim to make in an interview
Swift’s concurrency model provides compile-time-enforced freedom from data races — not freedom from concurrency bugs in general.
One-liner if pressed: “Swift enforces data-race freedom at compile time via actor isolation and Sendable checking — if it compiles under Swift 6 strict concurrency, you provably don’t have unsynchronized memory access across threads — but it explicitly does not, and cannot, catch reentrancy bugs, deadlocks, or anything hidden behind @unchecked Sendable or C interop, since those are logic or opt-out problems, not aliasing problems.”