Swift posts
No unambiguous salary cohort is available for this topic.
Interview questions · Worked answers
Strong Swift answers describe which values are copied and which references remain shared, identify the exact lifetime and isolation boundary, and show how cancellation or failure reaches owned work. They distinguish compiler-enforced data-race safety from higher-level invariants and from platform assumptions. The examples below were compiled with Swift 6.3.3 in Swift 6 mode unless marked compiler-negative.
Question 1 of 14
Swift structs and enums have value semantics: assigning or passing a value behaves as a separate value. Classes are reference types, so a copied reference still reaches the same instance. A value type can contain a class reference, so copying the outer value does not recursively clone the referenced object. Standard library collections such as Array use copy-on-write to avoid eagerly copying storage, while preserving the collection's value behavior when mutation occurs.
Consider a struct containing an array of class instances. Assigning the struct copies the array value, and later appending to one array does not append to the other. But both arrays still contain references to the same class instances. Mutating a referenced instance through either array is visible through both. This distinction is useful for API review: value syntax alone does not prove the entire reachable object graph is isolated or immutable.
Copy-on-write is an implementation strategy for standard library value collections, not a deep-copy guarantee for elements. When a collection is shared, a mutation that changes its storage separates the collection buffers as needed; it does not clone class instances stored inside. If a snapshot must be independent, define the snapshot boundary and explicitly map mutable reference elements into values or fresh instances. Avoid claiming every custom struct gets copy-on-write automatically; that behavior requires an implementation that provides it.
final class Box { var value: Int; init(_ value: Int) { self.value = value } }
struct Snapshot { var boxes: [Box] }
@main struct Demo {
static func main() {
let shared = Box(1)
let first = Snapshot(boxes: [shared])
var second = first
second.boxes.append(Box(2))
second.boxes[0].value = 9
print(first.boxes.count, second.boxes.count, first.boxes[0].value)
}
}Observed output: 1 2 9. Appending changes only the second Array value, but mutating the first shared Box reference changes the instance visible from both values.
Executed with Apple Swift 6.3.3 using `swiftc -parse-as-library -module-cache-path /tmp/swift-depth-work/module-cache -swift-version 6 -strict-concurrency=complete`.
Store immutable value elements, or create independent element values at the snapshot boundary. If reference types must remain, make their mutable state actor-isolated or synchronized and document the sharing contract.
Read the source: The Swift Programming Language: Structures and Classes · The Swift Programming Language: Memory Safety
Question 2 of 14
Swift enforces exclusive access to mutable memory: while a location is being accessed for mutation, another overlapping access to that location cannot occur. inout gives a function temporary exclusive access to its argument for the duration of the call. A nested read or write that overlaps that access can be rejected at compile time or trap at runtime when exclusivity cannot be proven statically.
The rule applies to storage access, not merely to whether two source expressions use the word inout. For example, mutating one array element while also reading the same array to calculate the new value can create overlapping access to the array's storage. The compiler may diagnose a statically visible overlap; dynamic exclusivity checks cover cases whose overlap can only be known at runtime. The goal is to prevent mutations from invalidating an access that is still in progress.
A practical repair is to separate the read from the write: calculate a value before opening the mutable access, or change the API to accept the needed input and return a replacement value. This is often clearer than disabling exclusivity checks. For APIs that mutate multiple properties, reason about whether those properties share one value's storage and whether a helper call re-enters the same object while mutation is active.
func modifyBoth(_ first: inout Int, _ second: inout Int) { first += second }
@main struct Demo {
static func main() {
var balance = 10
modifyBoth(&balance, &balance)
}
}Compilation fails because the same storage is passed as two overlapping mutable inout accesses. Make the ownership explicit: var adjustment = balance; modifyBoth(&balance, &adjustment) compiles and leaves balance equal to 20. This avoids aliasing the two mutable parameters.
Compiler-negative and repaired form executed with Apple Swift 6.3.3 using `swiftc -parse-as-library -module-cache-path /tmp/swift-depth-work/module-cache -swift-version 6 -strict-concurrency=complete`; diagnostic class verified, wording can vary by compiler build.
It can remove an overlapping read of a value, but it may be expensive and does not repair aliasing in a reference graph. Prefer an API whose inputs and mutation interval make ownership clear.
Read the source: The Swift Programming Language: Memory Safety · Swift Evolution SE-0176: Enforce Exclusive Access to Memory
Question 3 of 14
ARC keeps a class instance alive while strong references point to it. A stored closure can strongly capture its owner, forming a cycle: owner to closure to owner. A capture list changes the closure’s ownership of captured references. weak creates an optional, nonretaining reference that becomes nil when its object deallocates. unowned is also nonretaining and may be optional or nonoptional; you must keep a nonnil unowned reference valid or clear it yourself, because checked access after deallocation traps.
The ownership cycle depends on a strong edge from the stored closure back to its owner. For example, an explicit capture-list snapshot [strongOwner = instance] makes the stored closure retain that instance; the instance retains the closure, so clearing an unrelated local variable releases neither. The cycle is instance → stored closure → strong capture → instance. A weak capture breaks that edge, but the closure must handle the owner disappearing by unwrapping the optional reference or making the work unnecessary. A weak capture is not a generic fix if the operation must keep the owner alive until completion.
Use unowned only where a real ownership invariant proves the captured object outlives every possible closure call. An optional unowned property is useful for a non-owning relationship that can be absent, but ARC does not clear it like weak; the owner must set it to nil before its target disappears. A callback can arrive after cancellation or teardown, so a nonoptional unowned assumption is often weaker than it first appears. Another option is to clear the stored closure when the operation finishes or is canceled, which can preserve strong access during the operation while still breaking a long-lived cycle. Explain the intended lifetime and test the release path, not only the happy callback.
final class Owner {
var callback: (() -> Void)?
deinit { print("released") }
}
@main struct Demo {
static func main() {
var owner: Owner? = Owner()
owner?.callback = { [weak owner] in print(owner != nil) }
owner = nil
print("owner cleared")
}
}Observed output: released then owner cleared. The weak capture lets ARC release the instance when the external strong reference is cleared. With an explicit strong capture-list snapshot [owner] instead of [weak owner] in the stored callback, the instance and callback would retain one another and released would not print before process exit.
Executed with Apple Swift 6.3.3 using `swiftc -parse-as-library -module-cache-path /tmp/swift-depth-work/module-cache -swift-version 6 -strict-concurrency=complete`.
When the operation is meant to keep the captured object alive until completion and another owner or cancellation policy guarantees that completion. Still clear any stored closure afterward so a completed owner does not retain an unnecessary graph.
Read the source: The Swift Programming Language: Automatic Reference Counting · The Swift Programming Language: Unowned Optional References
Question 4 of 14
No. async marks a function that can suspend, and await marks a possible suspension point. A suspended task can let other work progress, but sequentially awaiting two operations still waits for the first before starting the second. Swift does not promise that an async function resumes on the same thread. Use structured concurrency such as async let when independent child operations should overlap, and use actor isolation to express where state access belongs.
An asynchronous function is not itself a thread or a parallel task. It can suspend at an await, allowing the executor to schedule other work, then resume later. The work between suspension points is not preempted by Swift's async syntax. This distinction matters for CPU-heavy work: simply marking a function async does not guarantee it leaves the main actor or runs simultaneously with another function.
To overlap independent calls, create child tasks explicitly and await their results after both have been started. Structured lifetime means the parent remains responsible for the child work; it does not promise a particular thread or that the operating system will run both at exactly the same time. If two operations depend on one another, use sequential awaits. If they are independent, async let can express the overlap without creating detached work.
func value(_ n: Int) async -> Int { n * 2 }
@main struct Demo {
static func main() async {
async let a = value(3)
async let b = value(4)
print(await a + b)
}
}Observed output: 14. Both child computations are started before the parent awaits their values. This proves the result and task structure, not a particular thread assignment or measurable parallel speedup for this tiny computation.
Executed with Apple Swift 6.3.3 using `swiftc -parse-as-library -module-cache-path /tmp/swift-depth-work/module-cache -swift-version 6 -strict-concurrency=complete`.
Move the work to an explicitly appropriate execution/isolation boundary, keep UI state updates on MainActor, and measure the relevant target. An async signature alone does not make synchronous parsing nonblocking.
Read the source: The Swift Programming Language: Concurrency
Question 5 of 14
No. A task group gives child tasks a structured lifetime: the group scope does not finish until its children finish, and cancellation propagates through the task hierarchy. Adding one child for every input still creates work for every input; structured concurrency is not a concurrency limit or a backpressure policy. Bound the number of active children with a fixed worker set, a sliding window, or another explicit admission mechanism.
A common mistake is to add a task for every row in a large collection and assume the runtime will choose a safe number to execute. Even if execution is scheduled, the program still allocates task state and retains captured inputs and results. The downstream service may also reject the fan-out. If the input count is unbounded or large, control both the number of in-flight child operations and how much queued input/results remain retained.
One pattern is to start at most K child tasks, then add one more whenever a child completes. Another is to launch K long-lived workers that pull from a bounded source. Choose based on ordering, cancellation and error semantics. The group still guarantees child lifetime at scope exit; the application must decide the numeric bound, how failures cancel siblings, and whether completed partial results should be returned.
func doubled(_ values: [Int], limit: Int) async -> [Int] {
precondition(limit > 0, "limit must be positive")
return await withTaskGroup(of: (Int, Int).self, returning: [Int].self) { group in
var next = 0
var results = Array<Int?>(repeating: nil, count: values.count)
for _ in 0..<min(limit, values.count) {
let index = next; next += 1
group.addTask { (index, values[index] * 2) }
}
while let (index, result) = await group.next() {
results[index] = result
if next < values.count {
let index = next; next += 1
group.addTask { (index, values[index] * 2) }
}
}
return results.map { $0! }
}
}
@main struct Demo {
static func main() async {
print(await doubled([], limit: 1))
print(await doubled([7], limit: 1))
print(await doubled([1, 2, 3, 4], limit: 2))
}
}Observed output: [], [14], and [2, 4, 6, 8]. Empty, one-element and multiple-element inputs work when the positive limit is met; at most the specified number of children are outstanding, and results are restored to input order. The API rejects zero and negative limits with its explicit precondition before scheduling any work.
Executed with Apple Swift 6.3.3 using `swiftc -parse-as-library -module-cache-path /tmp/swift-depth-work/module-cache -swift-version 6 -strict-concurrency=complete`.
Specify whether the first failure cancels siblings, whether siblings are awaited during cleanup, and whether partial results are discarded or returned. Make each child observe cancellation and release resources it owns.
Read the source: The Swift Programming Language: Tasks and Task Groups · Swift standard library: TaskGroup
Question 6 of 14
Cancellation in Swift is cooperative. Calling cancel() marks a task as canceled and propagates cancellation to structured child tasks, but arbitrary code does not stop automatically. A task must check Task.isCancelled, call Task.checkCancellation(), or await an operation that responds to cancellation. A parent scope that owns structured children still waits for them to finish, so a child that ignores cancellation can delay scope exit.
Cancellation is a request and state signal, not thread termination or a forced exception injected into every function. A CPU loop with no checks continues running after cancellation. A callback API may also continue its underlying operation unless the bridge explicitly cancels or unregisters it. Decide what cancellation means for the API: throw CancellationError, return partial work, or perform cleanup and return a domain result. Then check at useful boundaries rather than on every trivial instruction.
Cleanup is part of the cancellation contract. If a task owns a temporary file, network request or reservation, it needs a path that releases that resource when cancellation is observed. Structured concurrency preserves lifetime accounting by waiting for children before leaving scope; that can be a correctness benefit and a latency cost. A timeout implemented by canceling a task is only prompt if the operation cooperates or the underlying API has its own bounded cancellation behavior.
func sumUntilCancelled(_ values: [Int]) throws -> Int {
var total = 0
for value in values {
try Task.checkCancellation()
total += value
}
return total
}
@main struct Demo {
static func main() async {
let task = Task { () throws -> Int in
withUnsafeCurrentTask { $0?.cancel() }
return try sumUntilCancelled(Array(1...10))
}
do { print(try await task.value) }
catch { print(error is CancellationError) }
}
}Observed output: true. The task cancels itself before entering the loop, so the first explicit cancellation check throws CancellationError deterministically. The example does not claim that cancellation interrupts a blocking system call.
Executed with Apple Swift 6.3.3 using `swiftc -parse-as-library -module-cache-path /tmp/swift-depth-work/module-cache -swift-version 6 -strict-concurrency=complete`.
Bridge the task's cancellation to the callback API's own cancellation token or unregister operation, and synchronize cancellation against completion so the continuation is resumed exactly once. If the API cannot cancel, state that the task may wait for completion while ignoring or cleaning up the late result.
Read the source: The Swift Programming Language: Task Cancellation · Swift standard library: Task.cancel()
Question 7 of 14
Task {} creates an unstructured task that, by default, inherits actor isolation, priority and task-local values from its creation context. It is still unstructured: the caller must decide who keeps its handle, observes its result and cancels it. Task.detached {} does not inherit that context by default and therefore needs explicit decisions about isolation, priority, task locals and lifetime. Detached is not a generic background-work switch.
A task created with Task {} is useful when a synchronous boundary must start asynchronous work in the current context, for example an event handler that starts a request. In a main-actor-isolated handler, the closure inherits that isolation by default, so CPU-heavy synchronous work inside it can still occupy the main actor. Store the returned handle if the operation belongs to a view or service lifetime, and cancel it when that owner no longer wants the work. Dropping the handle does not cancel the task.
A detached task is independent from the creating task's actor context and task-local values by default. That can be right for work with a deliberately independent lifetime, but it can also lose tracing context, priority intent or actor safety. Pass only data that is safe to transfer, and route actor-isolated updates back through the appropriate actor. Avoid describing either constructor as guaranteed to use a particular thread or pool.
@MainActor final class Model {
private(set) var value = 0
func refresh() {
let task = Task { self.value = 7 }
_ = task // retaining it is needed if this owner must cancel or await it later
}
}This compiles because the Task closure inherits the enclosing main-actor isolation by default, so it can update the model. It makes no assumption about the executor used by a nonisolated async function. The unstructured task continues independently of the local handle; a production owner should retain the handle if it needs to cancel or await the work.
Compiled with Apple Swift 6.3.3 using `swiftc -parse-as-library -module-cache-path /tmp/swift-depth-work/module-cache -swift-version 6 -strict-concurrency=complete -emit-module`; no runtime output asserted.
Task.detached?Define which values may cross the isolation boundary, how any Sendable requirements are met, how actor-owned state is updated afterward, and who owns cancellation and result handling. Keep relevant task-local tracing or priority explicit if required.
Read the source: The Swift Programming Language: Unstructured Concurrency · Swift standard library: Task
Question 8 of 14
Yes. Actor isolation prevents simultaneous access to isolated state, but an actor-isolated async method can be suspended at await. While it is suspended, another task can run on the actor and change that state. When the original method resumes, its earlier checks may be stale. Re-check the invariant after suspension or reserve the state before awaiting, then commit or roll back deliberately.
Actors provide data-race safety and serialized access to isolated state; they do not make a multi-step async method an indivisible transaction. The actor can process another message during a suspension point. A check-then-await-then-write sequence can therefore accept two operations based on the same old state even though no two actor-isolated instructions ran simultaneously. This is a logical race rather than an unsynchronized memory race.
Design around the await boundary. For a reservation, either change the state before suspension to represent an in-progress claim, or revalidate after awaiting and reject/compensate if the condition no longer holds. If a call to another actor or external service can fail, decide how the intermediate state is repaired. Removing await merely to block the actor can create deadlocks or starvation; instead, make the invariant and transition explicit.
actor Gate {
var waiters: [CheckedContinuation<Void, Never>] = []
func rendezvous() async {
await withCheckedContinuation { continuation in
waiters.append(continuation)
if waiters.count == 2 {
let ready = waiters; waiters = []
ready.forEach { $0.resume() }
}
}
}
}
actor Stock {
var available = 1
func reserve(at gate: Gate) async -> Bool {
guard available > 0 else { return false }
await gate.rendezvous()
available -= 1
return true
}
}
@main struct Demo {
static func main() async {
let stock = Stock(), gate = Gate()
async let first = stock.reserve(at: gate)
async let second = stock.reserve(at: gate)
print(await first, await second, await stock.available)
}
}Observed output: true true -1. The gate releases only after both reserve calls have passed the initial availability check and suspended. The actor then runs each decrement serially, but both decisions used the pre-suspension value of one. A corrected method re-checks after the rendezvous; then exactly one reservation succeeds and availability ends at zero.
Executed with Apple Swift 6.3.3 using `swiftc -parse-as-library -module-cache-path /tmp/swift-depth-work/module-cache -swift-version 6 -strict-concurrency=complete`.
Yes, if the invariant-changing check and decrement happen in one actor-isolated, non-suspending operation. If work must suspend between them, model a reservation state or re-check and handle the losing operation explicitly.
Read the source: Swift Evolution SE-0306: Actors, reentrancy · The Swift Programming Language: Actors
Question 9 of 14
Sendable marks values that can safely cross concurrency domains under the type's semantic contract. Swift checks common safe cases, such as value types composed of sendable stored properties and immutable or actor-isolated reference types. @unchecked Sendable tells the compiler to trust the conformance without checking the safety proof; the author must ensure shared mutable state is synchronized or otherwise safe.
An actor method call can transfer arguments across an isolation boundary. A mutable class with no synchronization is unsafe to share this way because two tasks could access its state concurrently. A struct wrapper does not fix that if it contains a reference to the same mutable class. The compiler's checking is valuable precisely because it follows the type boundary rather than relying on a comment that the caller 'won't race'.
Use @unchecked Sendable only when the implementation has a concrete synchronization invariant that the compiler cannot express. Document which lock, actor or immutability rule protects every mutable field, then test concurrent access and teardown. It does not add a lock or make a type safe at runtime. In a migration, first use Swift 6 diagnostics to locate crossings and redesign ownership; do not blanket-annotate the code to silence the compiler.
final class Box { var value = 0 }
actor Store { func accept(_ box: Box) {} }
@main struct Demo {
static func main() async {
let box = Box()
await Store().accept(box)
box.value = 1 // using the transferred reference again is rejected
}
}Compilation fails because the non-Sendable mutable class instance is passed across the actor boundary and then used again in the caller. Swift 6 region-based isolation can permit a one-way transfer when the caller stops using the value; this example deliberately reuses it to demonstrate the unsafe shared case. A Sendable immutable value, actor-owned mutation, or proven synchronization design is required. Adding @unchecked Sendable without a safety proof only hides the diagnostic.
Compiler-negative checked with Apple Swift 6.3.3 in Swift 6 mode using `swiftc -parse-as-library -module-cache-path /tmp/swift-depth-work/module-cache -swift-version 6 -strict-concurrency=complete`; exact diagnostic wording varies by compiler build.
When its state is immutable after initialization, isolated to a global actor, or protected by synchronization that is used consistently for every access. Make the guarantee part of the implementation and API contract.
Read the source: Swift standard library: Sendable · Swift standard library API: Sendable
Question 10 of 14
@MainActor is a global actor isolation contract: its isolated state and functions are accessed through the main actor. It is closely associated with the main thread on Apple platforms, but actor isolation is the language-level rule and thread identity is a lower-level execution detail. Calling a main-actor function from a context that is not statically isolated to that actor requires async call syntax with await; that does not guarantee a physical thread or executor switch if execution is already on the required actor. Expensive synchronous work inside a main-actor function still blocks main-actor progress.
Annotating a model or UI mutation with @MainActor lets the compiler check that access is serialized with other main-actor work. It does not automatically move every operation in a call chain to a background executor. If a main-actor function performs a large synchronous decode before returning, the main actor remains occupied during that work. An await can permit other actor work to run, but it does not preempt synchronous computation that has not reached a suspension point.
Keep UI state updates on the actor that owns them, and make expensive work’s execution boundary explicit. A cross-actor call has an isolation requirement and may suspend, but do not infer that the runtime must switch threads or executors on every call. Platform frameworks may impose their own main-thread requirements, so verify those API contracts separately. Do not test the design by asserting a thread ID for ordinary Swift async code; test actor access and visible UI behavior, and use profiling to find stalls.
@MainActor final class ScreenState {
private(set) var title = "Loading"
func display(_ value: String) { title = value }
}
func update(_ state: ScreenState) async {
await state.display("Ready")
}This compiles because the nonisolated async function uses await to access the main-actor-isolated method. The language checks the isolation boundary; it does not guarantee a physical thread or executor switch for this call.
Compiled with Apple Swift 6.3.3 using `swiftc -parse-as-library -module-cache-path /tmp/swift-depth-work/module-cache -swift-version 6 -strict-concurrency=complete -emit-module`; no runtime output asserted.
Keep the state mutation isolated to MainActor, move the parsing behind an explicitly appropriate non-main execution boundary, and return a sendable result before applying it. Verify the actual framework and deployment target behavior.
Read the source: The Swift Programming Language: The Main Actor · Swift standard library: MainActor
Question 11 of 14
A continuation must be resumed exactly once. A missing callback leaves the task suspended forever; resuming a checked continuation more than once is a programming error diagnosed at runtime. A bridge must also define what happens when the callback races with cancellation: unregister or cancel the legacy operation where possible, serialize the completion decision, and ensure only one path resumes the continuation.
Continuations are an interoperability tool for adapting an existing callback API, not a way to turn blocking synchronous code into safe async code. The callback may be invoked synchronously during registration or later, and a cancellation handler may run concurrently with it. A correct bridge therefore needs a small state machine that arbitrates completion, cancellation and possibly registration failure. The state transition must decide who owns the one permitted resume.
Use withCheckedContinuation or withCheckedThrowingContinuation when the callback contract is exactly once, and preserve the callback's result or error. If the underlying API offers cancellation, connect task cancellation to that token and handle late completion without a second resume. If it cannot cancel, cancellation may only stop waiting or ignore the eventual result if the bridge can safely settle the continuation; state that limitation rather than promising immediate stop.
func legacyValue(completion: @escaping (Int) -> Void) { completion(42) }
func value() async -> Int {
await withCheckedContinuation { continuation in
legacyValue { result in continuation.resume(returning: result) }
}
}
@main struct Demo { static func main() async { print(await value()) } }Observed output: 42. The sample callback invokes once synchronously, so the continuation resumes exactly once. It intentionally does not claim cancellation support; a real asynchronous API needs an explicit cancellation/completion race policy.
Executed with Apple Swift 6.3.3 using `swiftc -parse-as-library -module-cache-path /tmp/swift-depth-work/module-cache -swift-version 6 -strict-concurrency=complete`.
Fix or wrap the legacy contract with synchronized one-shot state before resuming. Do not rely on a flag that is read and written from multiple concurrent callback paths without synchronization.
Read the source: Swift Evolution SE-0300: Continuations for interfacing async tasks with synchronous code · Swift standard library: withCheckedContinuation
Question 12 of 14
Coordinate the tasks with explicit test-controlled signals, barriers, continuations or actor state, then assert the result after all relevant work has completed. Avoid sleep as proof that another task reached a point: scheduler load changes when it runs, so a delay can both make a correct test flaky and let a broken implementation pass. Test the final invariant and cancellation/resource effects, not a guessed interleaving.
A deterministic test creates the desired interleaving directly. For example, a fake dependency can suspend requests until the test has observed both arrivals, then release them together. The test can assert that exactly one reservation succeeded and that the remaining count is zero. This is stronger than pausing for a fixed duration and checking whether two tasks probably overlapped.
Concurrency tests also need explicit completion boundaries. Await child task values or a test-owned completion signal before inspecting actor state. For cancellation, prove the child observed cancellation and cleanup ran; do not infer completion from a call to cancel(). Keep test fixtures isolated or synchronized themselves, because a racy test double can obscure whether production code is correct.
actor Counter { var value = 0; func increment() { value += 1 } }
@main struct Demo {
static func main() async {
let counter = Counter()
await withTaskGroup(of: Void.self) { group in
for _ in 0..<100 { group.addTask { await counter.increment() } }
}
print(await counter.value)
}
}Observed output: 100. The task-group scope waits for all 100 children, each actor-isolated increment is serialized, and the final read happens only after the children complete. This checks a stable invariant without depending on completion order or a sleep interval.
Executed with Apple Swift 6.3.3 using `swiftc -parse-as-library -module-cache-path /tmp/swift-depth-work/module-cache -swift-version 6 -strict-concurrency=complete`.
Inject a test gate that releases only after both operations have passed the initial check, then assert the buggy implementation's violated invariant and the repaired implementation's one-winner result. This makes the ordering part of the fixture contract rather than a timing guess.
Read the source: The Swift Programming Language: Tasks and Task Groups · Swift Testing documentation
Question 13 of 14
A generic constraint names a concrete type parameter chosen by the caller and lets the function express relationships between values of that type. An opaque return type (some P) hides one specific underlying type chosen by the implementation while keeping its identity consistent to the compiler. An existential (any P) stores a value of a conforming type selected at runtime, and can hold different conforming types in one collection, but clients can use only the protocol requirements without a runtime cast.
Choose the abstraction from the relationship the API must preserve. A generic such as func sameID<T: Key>(_ a: T, _ b: T) states that both arguments share one concrete type. An opaque return such as func makeKey() -> some Key hides which one concrete type the implementation returns, while callers can still pass the result to generic code because the compiler preserves its underlying identity. A function returning some Key must return a consistent underlying type for its declaration; it cannot choose UserKey on one branch and OrderKey on another unless those are wrapped in one common concrete type.
An any Key value is a box around some conforming runtime value. It is useful for heterogeneous storage, for example a list containing user and order keys, when the API only needs shared requirements such as id. The box does not expose concrete-only members, and a protocol requirement involving an associated type or Self can impose additional limits on existential use. Prefer a generic or opaque type when type identity and relationships matter; choose an existential when runtime heterogeneity is the actual requirement. Do not claim a universal performance winner: the existential flexibility can require indirection, while real cost depends on the interface and optimization context.
This use of some is in the return position. A parameter declared with some P is shorthand for a generic parameter chosen by the caller; it is not an implementation-selected opaque return type. Specify the position before comparing these forms.
protocol Key { var id: String { get } }
struct UserKey: Key { let id: String }
struct OrderKey: Key { let id: String }
func label<T: Key>(_ key: T) -> String { "\(String(describing: T.self)):\(key.id)" }
func makeKey(_ id: String) -> some Key { UserKey(id: id) }
@main struct Demo {
static func main() {
let hidden = makeKey("u7")
print(label(hidden))
let mixed: [any Key] = [hidden, OrderKey(id: "o2")]
print(mixed.map(\.id).joined(separator: ","))
}
}Observed output: UserKey:u7 followed by u7,o2. The opaque factory returns one concrete UserKey, whose identity remains available to the generic label call without being named in the factory signature. The existential array stores two different conforming types and exposes their common id requirement.
Executed with Apple Swift 6.3.3 using `swiftc -parse-as-library -module-cache-path /tmp/swift-depth-work/module-cache -swift-version 6 -strict-concurrency=complete`.
Store [any Key] if the common id contract is sufficient, and define equality or identity in that protocol contract. If operations need the concrete type relationship between items, retain a generic collection type or add a type-erased wrapper that explicitly implements the needed operations.
Read the source: The Swift Programming Language: Protocols as Types · The Swift Programming Language: Opaque and Boxed Protocol Types · Swift Evolution SE-0335: Introduce existential any
Question 14 of 14
Ordinary throws exposes the broad any Error contract and remains the right default when the error set can evolve or callers should handle failures generically. Typed throws such as throws(LoadError) communicates a specific error type when the set is narrow and stable, so callers can handle those cases exhaustively and generic code can preserve the error type. It is an API contract with compatibility consequences, not a requirement to type every throwing function.
With ordinary throws, a caller knows a function can fail but does not receive a statically fixed concrete error type; the error is represented as any Error. A typed throwing function restricts thrown values to the declared error type. This lets a caller match the domain cases directly, and lets higher-order generic APIs propagate the same failure type rather than erase it. A function may still use ordinary throws for a lower-level operation whose future error cases should remain open to callers.
Use typed throws when the error family is genuinely closed or locally controlled and exhaustive handling is useful. Widening a public typed error contract later can break source that depends on its exact catch type, so choose the boundary deliberately. When several lower-level errors need context or a higher-level operation can introduce new failures, map them to a stable domain error or keep an untyped boundary. Result<Success, Failure> stores an error type in a value; typed throws instead keeps failure in Swift’s normal try/catch control flow, so they serve related but different call-site needs.
enum LoadError: Error { case missing, invalid }
func load(_ raw: String) throws(LoadError) -> Int {
guard !raw.isEmpty else { throw .missing }
guard let value = Int(raw) else { throw .invalid }
return value
}
@main struct Demo {
static func main() {
do { print(try load("17")) } catch { print("unexpected \(error)") }
do { _ = try load("bad") }
catch LoadError.invalid { print("invalid") }
catch { print("unexpected") }
}
}Observed output: 17 followed by invalid. The function can throw only LoadError, so the caller can name the expected case in its catch clause. The explicit fallback keeps the demonstration exhaustive if the enum later gains a case; a public API that adds cases must consider its evolution policy.
Executed with Apple Swift 6.3.3 using `swiftc -parse-as-library -module-cache-path /tmp/swift-depth-work/module-cache -swift-version 6 -strict-concurrency=complete`.
Usually not without a deliberately stable error abstraction. The client may need to pass through transport, decoding and server errors that evolve; use a broad boundary or map them into a versioned client error contract whose compatibility promises are explicit.
Read the source: The Swift Programming Language: Error Handling · Swift Evolution SE-0413: Typed throws
Salary context
These are Hacker News job posts that mention these technologies, not employee earnings or a measure of what a skill adds to pay. The amounts summarize annualized advertised minimums in approximate USD. Groups can overlap and differ in roles, locations and dates.
No unambiguous salary cohort is available for this topic.