Swift inout Exclusivity Interview Questions: Find Overlapping Access Before It Becomes a Runtime Error
Quick Overview
Trace Swift inout exclusivity through overlapping accesses, compiler rejections and a runtime trap, then test safe captures and disjoint local properties. Original worked cases, primary references and explicit verification boundaries support focused interview practice.
Swift inout exclusivity is about access duration and memory location, not only whether code runs on several threads. A conflict occurs when accesses overlap, reach the same storage and include a write, subject to the language's atomic-access rules. An inout argument receives a long-term write access for the call, so reading the original variable during that access can fail even on one thread.
This original Swift interview exercise distinguishes compiler-rejected aliases, a runtime-detected global conflict and safe alternatives that preserve the intended result. PracHub's senior iOS engineering practice provides broader platform context; this lab isolates language-level overlapping access.

The Swift book and official exclusivity article define the rules. Local cases ran under Swift 6.3.3 on arm64 macOS with checked exclusivity. Four safe cases passed, two sources were rejected by the compiler and one separately compiled program trapped at runtime. No iOS application, multithreaded race detector or unsafe-pointer experiment was executed.
Track location, access kind and duration
A read observes a value; a write modifies it or requires mutable access. Two instantaneous operations in an ordinary expression can often be sequenced safely. A long-term access is different because other code can execute before it ends. Function calls with inout parameters and mutating operations can create those longer access windows.
The Swift memory-safety chapter explains the relevant access characteristics. For our non-atomic examples, ask three questions: do the accesses reach the same storage, do their durations overlap, and does at least one access write? Naming only the variable spellings is insufficient because two parameters can alias the same location.
Distinguish an exclusivity violation from a data race. A data race involves unsynchronized conflicting access across threads. Swift's exclusivity violation can occur when a single-threaded function re-enters access to storage it is already modifying. Adding a mutex around the entire single-threaded call would not explain or repair the alias relationship inside that call.
Also distinguish storage identity from equal values. Two separate integers both equal to seven can be written concurrently within a call without being the same location. Conversely, two different parameter names can reach one original variable. An access diagram should label the storage and access windows, not merely the numbers currently printed.
Reject the same variable passed twice as inout
Our first invalid source passes one variable to two inout parameters. Each parameter needs write access for the call, and both reach the same storage. The compiler can see that alias at the call site and rejects the program before it runs.
func pair(_ a: inout Int, _ b: inout Int) {
a += b
}
var x = 1
pair(&x, &x) // expected compile-time rejection
The ampersand marks the special argument-passing operation; it does not mean the language has waived safety because the programmer requested a pointer. Treating &x as a generic escape hatch misses the reason inout is useful: it permits controlled mutation under an access contract.
Two distinct variables are different. Our successful distribute function reads their total and divides it between them. Passing separate left and right variables is valid, and starting from four and ten produces seven and seven. The result verifies the intended mutation while the distinct locations satisfy the access requirement.
func distribute(_ a: inout Int, _ b: inout Int) {
let total = a + b
a = total / 2
b = total - a
}
var left = 4
var right = 10
distribute(&left, &right) // left == 7, right == 7
A proposed repair that copies one argument must still preserve the application's intended semantics. If both arguments deliberately represented one logical quantity, writing back two temporary results could have ambiguous precedence. Clarify what the operation should mean before replacing a compiler error with a sequence that happens to compile.
Find the read hidden inside a callback
A callback can access the same original variable while an outer function holds it as inout. The syntax looks less direct than passing &x twice, but the access relationship is the same: one long-term write window contains another access to the original storage.
func modify(_ value: inout Int, _ body: () -> Int) {
value += body()
}
func bad() {
var x = 1
modify(&x) { x } // expected compile-time rejection in this fixture
}
The closure does not need to escape or run asynchronously to create the conflict. It is called synchronously while modify still holds mutable access. Explaining this timing is more useful than saying closures are dangerous, because many closures operate on unrelated data and create no exclusivity problem.
In the local lab, this source is compiled separately and must fail. It is not included in the successful executable. That separation lets the test record both a meaningful compiler diagnostic and the safe cases without turning the whole exercise into an unbuildable project.
When reviewing a more complex callback, inspect what it captures and what the enclosing operation already accesses. A helper several calls away may read the original storage through a global or captured reference. The compiler's ability to prove the conflict varies with how that access is expressed; the language rule does not disappear when the alias becomes less obvious.
Observe a runtime conflict through global storage
Our dynamic case uses a global variable. update receives it as an inout argument and then reads the same global name inside the body. The compiled program starts the outer write access, reaches the inner read and traps under checked exclusivity.
var globalStep = 1
func update(_ value: inout Int) {
value += globalStep
}
update(&globalStep) // runtime access conflict in the recorded build
The official Swift exclusivity article explains why runtime enforcement complements static checking. Some relationships are discovered dynamically rather than fully rejected during compilation. A source file producing an executable therefore is not evidence that every runtime access path satisfies exclusivity.
The local command uses swiftc -Onone -enforce-exclusivity=checked. The runtime probe is a separate process, and its nonzero termination with the expected access-conflict diagnostic is the test's intended result. We do not continue running application logic after the trap or describe the crashed program as a successful business operation.
Do not propose disabling exclusivity checks as the repair. That removes evidence of an invalid access pattern without defining what the operation should mean. The useful change is to shorten an access, separate storage or redesign the function's inputs so the necessary read occurs outside the conflicting mutable window.

Capture the intended read before beginning mutation
For the global-step example, suppose the requirement is to add the original value to itself. Capture that read first and pass the captured value as an ordinary parameter. The original read then ends before the mutable operation begins, and the function does not reach back into the same original variable through another name.
var base = 5
let captured = base
func add(_ value: inout Int, delta: Int) {
value += delta
}
add(&base, delta: captured)
// base == 10
This safe case is executed in the lab. It makes the temporal decision explicit: use the value from before mutation. If the intended requirement instead depends on an updated value midway through the operation, this copy changes semantics and is not automatically the right fix. State which version of the data the calculation needs.
The Swift book notes that non-inout arguments are evaluated before the long-term accesses to inout arguments begin. That matters when reasoning about argument expressions. Our explicit captured constant avoids relying on a reader reconstructing that sequencing from a dense call expression and keeps the requirement visible during an interview explanation.
Arrays deserve the same storage-level care. The successful fixture reads values[1] into a constant before adding it to values[0], producing [10,8] from [2,8]. It does not claim that passing two dynamically indexed subscripts as separate inout arguments is universally accepted merely because the indexes happen to differ.
For a reusable API, consider returning a new value or accepting a closure that receives the already borrowed value rather than capturing the original. Those designs can make aliasing relationships easier to express. They still need tests for the chosen behavior; changing the function shape alone is not evidence of preserved semantics.
Understand the exception for disjoint local stored properties
A value's properties are part of a larger storage value, so modifying a property can require access to that containing value. However, Swift can prove some simultaneous accesses to different stored properties safe when the value is local and is not captured or otherwise exposed in a conflicting way.
The successful fixture defines a local Pair structure with stored left and right properties, then passes those distinct properties to distribute. It compiles and produces seven and seven. This is a concrete permitted case, not a claim that every pair of properties on every object or global structure is independent.
func localProperties() {
struct Pair { var left = 4; var right = 10 }
var p = Pair()
distribute(&p.left, &p.right)
precondition(p.left == 7 && p.right == 7)
}
Computed properties, captured values, global storage and subscripts can change what the compiler can prove and what accesses the operation performs. Distinct property names alone do not establish separate safe accesses. Check whether the value is local, whether the properties are directly stored and whether other access paths can reach the containing storage during the call.
An interview answer should therefore avoid both extremes: “different fields always conflict” and “different fields are always safe.” The useful explanation is a rule with a demonstrated local stored-property exception and a clearly stated boundary. If a modified example changes that boundary, compile it independently rather than guessing from the earlier successful case.
Keep exclusivity separate from physical copying and ownership
inout can be explained with copy-in/copy-out semantics, but the compiler may optimize how values are passed. The exclusivity rule is a language contract, not a promise that a particular number of physical copies occurred. Neither a correct result nor a stack trace measures physical copying cost.
Likewise, ARC lifetime and exclusivity are related to memory safety but answer different questions. Keeping an object alive does not authorize two conflicting accesses to the same mutable value. A weak capture that avoids a retain cycle is not an automatic solution to the closure alias in this lab.
The four positive cases, two compiler rejections and one runtime trap are bounded evidence for Swift 6.3.3 on the recorded platform and flags. They do not cover every optimizer configuration, unsafe pointer escape, actor-isolation rule or cross-thread race. A production change should retain representative integration checks in addition to the small language regression.
When explaining a failed repair, draw the access windows again after the change. Renaming a closure capture or placing the same read in another helper does not necessarily move it outside the outer write. The safe captured-constant case changes timing and storage relationships: the read ends before the call, and the function reads the independent constant. That difference, rather than the new variable name, explains why the result can be computed without the original overlap. Preserve this explanation when refactoring the helper later.
Practice identifying the storage before proposing a fix
The selected records include Swift value semantics, ARC and dictionary design, plus transferable validation and ownership exercises. They are not five dedicated exclusivity questions. The C++ container record uses different language rules; use it only to compare what a safety argument must identify, not to import C++ aliasing semantics into Swift.
| PracHub question | Reasoning to rehearse |
|---|---|
| Explain Swift memory, value semantics, and GCD | Separate value identity, memory management and execution behavior. |
| Explain iOS ARC and avoid retain cycles | Explain why object lifetime does not resolve an access conflict. |
| Implement a dictionary without built-in Dictionary | Identify which mutable storage a data-structure operation reaches. |
| Design input validation and error handling | Define the intended result before replacing an invalid mutation pattern. |
| Implement a Simplified std::vector in C++: Growth, Memory, Exception Safety, and Type Traits | State the language-specific invariants behind a safe public API. |
Use the Swift memory question to draw the outer inout write interval and the inner global read. Then replace the read with a captured input and show the preserved result. A defensible answer explains the location, overlap and intended semantics before offering a syntactic workaround.
Sources and Further Reading
Sources checked November 3, 2026. Tests use Swift 6.3.3 with checked exclusivity on arm64 macOS; multithreaded races and production iOS integration remain outside the executed scope.
Comments (0)