Swift language cheat sheet

The 23 Swift language interview answers in 30 seconds each, plus quick facts. Printable, free.

One page per topic: print it or save it as PDF from your browser's print menu. Practise these questions

Optionals and why Swift has them

An optional is either a value or nil, written T?, and the compiler makes you handle the nil case before using it. That moves 'null pointer' crashes to compile time. Unwrap safely with if let, guard let, ?? or optional chaining; force unwrapping with ! is a deliberate crash if nil.

Value types vs reference types

Value types (structs, enums, tuples) are copied on assignment, so each variable owns its data. Reference types (classes, actors, closures) are shared: assigning copies the reference, and every variable sees the same instance. Prefer structs for data; use classes when you need identity, shared mutable state or inheritance.

Protocols vs abstract classes

A protocol defines requirements that any type (struct, enum, class or actor) can adopt, and protocol extensions add default implementations. Swift has no abstract classes; a base class with methods that crash if not overridden is the usual imitation. Protocols allow multiple conformances and work with value types, which is why Swift is called protocol-oriented.

Closures and capture lists

Closures capture variables by reference, so they see later changes. A capture list ([count]) copies the current value when the closure is created. [weak self] captures a weak reference to break retain cycles. Escaping closures that capture self strongly can keep objects alive.

@escaping vs non-escaping closures

Closure parameters are non-escaping by default: they must be called before the function returns, so the compiler knows they can't outlive the call. Mark a parameter @escaping when you store it or call it later, for example a completion handler. Escaping closures need explicit self. and are where retain cycles happen.

Generics

Generics let you write one function or type that works with many types while keeping full type safety. Constraints (T: Comparable) say what the type must support. The compiler can specialize generic code for each concrete type, so you keep performance without duplicating code or falling back to Any.

Associated types and primary associated types

An associated type is a placeholder a conforming type fills in, like Element in Collection. It makes the protocol generic. Since Swift 5.7, primary associated types (protocol Store<Item>) let you write constraints like some Store<User> or any Store<User> without verbose where clauses.

Enums with associated and raw values

Raw values give each case a fixed literal of one type (Int, String) and a failable init(rawValue:). Associated values attach different data to each case at runtime, like case success(Data) and case failure(Error). An enum can't have both on the same case, and switches over enums must be exhaustive.

willSet and didSet

willSet runs just before a stored property changes (with newValue), and didSet runs just after (with oldValue). They are not called while the type's own initializer sets the property, or when the property is set before the object is fully initialized. They are useful for small side effects like updating a label.

Stored, computed and lazy properties

A stored property keeps a value in the instance. A computed property has no storage; it runs its getter on every access. A lazy var is stored but initialized on first access, then reused. Use lazy for expensive setup that may never be needed; use computed for values derived from other state.

The defer statement

defer schedules code to run when the current scope exits, however it exits: return, throw or falling off the end. Multiple defers run in reverse order of declaration. It keeps cleanup next to setup, for example unlocking a lock or resetting a loading flag.

Result and typed throws

Result<Success, Failure> stores either a value or an error, which is useful for callbacks or keeping an outcome for later. Swift 6 adds typed throws: throws(ParseError) declares exactly which error a function can throw, so catch gets a ParseError with no casting. Plain throws (meaning any Error) is still the right default for most APIs.

inout parameters

An inout parameter lets a function change a caller's variable. The value is copied in, modified, and written back when the function returns (copy-in copy-out). You pass a mutable variable or a read-write property with &, including computed properties with a setter. Constants, literals and get-only properties can't be passed.

Key paths

A key path (\Person.age) is a type-safe reference to a property that you can store and pass around, then read with person[keyPath: kp]. Key path expressions can be used where a function is expected, such as map(\.name). For sorting, use a closure or Foundation's KeyPathComparator.

Opaque types (some) vs existentials (any)

some P is an opaque type: one specific concrete type that the compiler knows but hides from the caller, with static dispatch and no boxing. any P is an existential: a box that can hold any conforming type, chosen at runtime, with dynamic dispatch. Use some by default; use any when you really need different types in one place, like a heterogeneous array.

@autoclosure

@autoclosure wraps an argument expression in a closure automatically, so the caller writes a normal expression but it is evaluated only if and when the function calls it. It enables lazy evaluation, as in assert, ?? and &&. Use it sparingly, because callers can't see that evaluation is delayed.

Copy-on-write

Copy-on-write lets value types like Array, Dictionary and String share one buffer between copies until one of them is modified. Assigning is cheap; the real copy happens on the first write to a shared buffer. Your own structs get this only if you implement it with isKnownUniquelyReferenced.

Property wrappers

A property wrapper is a type marked @propertyWrapper with a wrappedValue that adds logic around a property's get and set, such as clamping, persistence or thread confinement. @State, @AppStorage and @Published are wrappers. A projectedValue is exposed with $.

== vs ===

== checks value equality and requires the type to conform to Equatable. === checks identity: whether two class references point to the same instance. For classes you can use === without Equatable; using == on a class that doesn't conform to Equatable is a compile error.

Swift macros

Macros (Swift 5.9) generate code at compile time from your source. Freestanding macros start with # and produce an expression or declarations, like #Preview or #expect. Attached macros start with @ and add code to a declaration, like @Observable, @Model or @Entry. Macros run in a sandbox and can be expanded in Xcode to inspect the output.

Phantom types

A phantom type is a generic parameter that is never stored, used only to give values different types at compile time. ID<User> and ID<Order> share an implementation but can't be mixed up. This catches bugs like passing an order ID where a user ID is expected, at no runtime cost.

Hashable

Hashable lets a type be used in sets and as dictionary keys. The rule: values that are equal must produce the same hash. Hash exactly the properties that == compares, in the same way. Swift synthesizes both when all stored properties are Hashable, which is usually best.

Noncopyable types (~Copyable)

A type marked ~Copyable can't be copied, only moved or borrowed. That gives unique ownership: exactly one owner, and a deinit that runs once. It fits resources like file descriptors, locks and tokens that must not be duplicated. Swift 6 extended generics and Optional to support noncopyable types.

Quick facts

let vs var
let binds a constant and var a variable. Prefer let; the compiler warns when var is never mutated.
Subscripts
subscript(index:) -> T adds [] access to your types, with optional setters and multiple parameters.
@discardableResult
Silences the unused-result warning. Use it only when ignoring the result is normal (like removeFirst), not to hide errors.
Nominal typing
Swift types are identified by name, not shape: two structs with the same fields are different types. Tuples and functions are structural.
Never
A function returning Never doesn't return (fatalError, preconditionFailure). The compiler knows code after it is unreachable.
@dynamicMemberLookup
Lets obj.anything compile by routing to a subscript(dynamicMember:), often with key paths for type safety.
Any vs AnyObject
Any holds any value, including structs and functions; AnyObject holds only class instances.
class vs static methods
Both belong to the type. static can't be overridden; class (classes only) can be overridden by subclasses.