Swift Interview Questions
Real Swift interview questions and answers on structs vs classes, optionals, and ARC.
A curated set of Swift interview questions, ordered roughly from fundamentals to more advanced — the kind you'll actually be asked in real screens and on-sites.
Type system fundamentals
Q: What's the core difference between a struct and a class in Swift? Structs have value semantics — assigning one to a new variable or passing it to a function creates an independent copy, so mutations to the copy never affect the original. Classes have reference semantics — assigning one shares the same underlying instance, so a mutation visible through one reference is visible through every reference to that instance. Structs also can't be inherited from; classes can.
Q: Why does Apple recommend defaulting to structs rather than classes?
Value semantics make code easier to reason about in isolation — you never have to worry that some other part of the program holds a reference to the same instance and could mutate it out from under you. Swift's own standard library collections (Array, Dictionary, String) are all structs for this reason; classes are reserved for cases that genuinely need shared, mutable identity or class inheritance.
Optionals
Q: Why did Swift introduce Optionals instead of just allowing any variable to hold nil, like Objective-C did?
Making "might have no value" part of the type system (String vs. String?) forces the compiler to require the value be checked and unwrapped before use, eliminating an entire category of nil-related crashes that were common and hard to trace back in Objective-C, where any object reference could silently be nil.
Q: What's the difference between if let, guard let, and force-unwrapping (!)?
if let binds the unwrapped value for use only inside that if block. guard let binds it for use in the rest of the enclosing scope but requires an early exit in its else branch, making it the idiomatic choice for validating preconditions at the top of a function. Force-unwrapping (!) skips the check entirely and crashes at runtime if the value is actually nil — appropriate only when nil there would represent a genuine programmer error, not routine control flow.
Memory management
Q: What is ARC, and what problem can it introduce that a garbage collector doesn't have in quite the same way?
ARC (Automatic Reference Counting) deallocates a class instance the moment its strong-reference count hits zero, giving deterministic, immediate cleanup with no GC pauses. Its specific risk is a retain cycle: two instances holding strong references to each other never reach a zero count, leaking memory even after nothing else references either of them — fixed by marking one side of such a relationship weak or unowned.
Protocols and design
Q: What is protocol-oriented programming, and how does it differ from classic inheritance-based OOP? Rather than building deep class inheritance hierarchies, protocol-oriented programming defines small, focused protocols and shares default behavior through protocol extensions. Because both structs and enums (not just classes) can conform to protocols and pick up those default implementations, it enables composition-style code reuse across value types — something inheritance alone can't do, since only classes support inheritance in Swift.
Q: When would you choose try? over a full do-catch block?
When the caller only cares whether an operation succeeded or failed, and doesn't need to distinguish between different failure reasons — try? converts the result into a simple Optional (nil on any failure). A do-catch is the right choice whenever different error cases genuinely need different handling, since it preserves the specific thrown error.
Concurrency and testing
Q: What is Swift's structured concurrency, and how does async let differ from a TaskGroup?
Structured concurrency (async/await, Task, async let, task groups) lets the compiler and runtime track the relationship between a task and the scope that created it, so cancellation propagates automatically and a task can't silently outlive its owner. async let is for a fixed, known-in-advance number of independent concurrent operations; a TaskGroup is for a dynamic number of concurrent child tasks, added in a loop, useful when that number is only known at runtime.
Q: What is an actor in Swift and what specific problem does it solve?
An actor is a reference type, like a class, except the compiler guarantees only one task can execute inside it at a time — every call into it from outside requires await. This prevents data races on its own mutable state without a manually acquired and released lock, since async/await alone governs suspension points but does nothing by itself to stop two concurrent tasks from racing on shared mutable state.
Q: What's the role of setUp()/tearDown() in an XCTestCase, and how do you test a throwing or async function?
setUp() runs before every test method to establish fresh state (so no state leaks between tests); tearDown() runs after each one for cleanup. A throwing function is tested by marking the test method itself throws and calling it with try, or wrapping it in XCTAssertThrowsError to inspect the specific error. An async function is tested by marking the test method async and using await directly — XCTest waits for the test's Future-like completion before reporting pass or fail.