← All topics/Swift language features

Senior / Staff iOS — interview prep

Swift language

value semantics · optionals · protocols · ARC · closures · errors · wrappers · COW · Sendable

struct vs classOptionalsProtocolsARCClosuresthrows / ResultProperty wrappersCOWSendableKey paths
01

Core mental model — types & semantics

Q1
Struct / enum
  • Value types — assignment copies (often COW under the hood)
  • Compared with ==
  • No inheritance; protocol conformance only
  • Default choice for models and pure data
Class
  • Reference type — shared identity, ===
  • ARC + retain cycles with delegates / closures
  • UIKit / Obj-C interop, subclassing
Actor
  • Reference type with isolated mutable state
  • Compiler serializes access — not a general “model” replacement
  • Pair with async/await at boundaries
Struct vs class — shape
// Value type — copy-on-write friendly, no inheritance
struct Profile: Equatable {
    var displayName: String
}

// Reference type — identity, ARC, subclassing
final class Session {
    var token: String
    init(token: String) { self.token = token }
}
Actor — isolated mutable state
actor Counter {
    private var value = 0
    func next() -> Int {
        value += 1
        return value
    }
}
i
Interview sound bite: Prefer structs for value semantics and thread-friendly copies; use classes for shared mutable identity and framework patterns; use actors for concurrent shared mutable resources — not for every view model.
02

Optionals — safety without pyramid code

Q2
guard, chaining, coalescing
func title(for user: User?) -> String {
    guard let user else { return "Guest" }
    return user.profile?.displayName?.trimmingCharacters(in: .whitespaces)
        ?? "Unknown"
}

// map / flatMap — avoid nested if let
let id = optionalString.flatMap(Int.init)
guard let
  • Nil means exit — invalid state or early return
  • Keeps the rest of the scope flat
if let
  • Both branches matter — optional UI or features
  • Avoid deep nesting; consider map/flatMap
Avoid !
  • Force-unwrap crashes on nil
  • Reserve for invariants (document why) or fail-fast dev-only paths
03

Protocols & generics

Q3
Associated types + constrained extension
protocol Identifiable {
    associatedtype ID: Hashable
    var id: ID { get }
}

extension Array where Element: Identifiable {
    func index(of element: Element) -> Int? {
        firstIndex { $0.id == element.id }
    }
}
Generics
  • where clauses refine requirements
  • Type erased at call site — concrete types at compile time
  • Prefer generics over Any when possible
Existentials
  • any Protocol — boxed, dynamic dispatch
  • Some protocols with associated types need any or generics
04

ARC — strong, weak, unowned

Q4
weak self in escaping closures
class DetailVC: UIViewController {
    var onDismiss: (() -> Void)?

    deinit { print("DetailVC gone") }

    func wire() {
        network.fetch { [weak self] result in
            guard let self else { return }
            self.apply(result)
        }
    }
}

// unowned — only when lifetime is provably nested (expert use)
// parent holds child; child never outlives parent
strong
  • Default — keeps object alive
  • Delegate / parent refs often need weak to avoid cycles
weak
  • Optional — becomes nil when deallocated
  • Use for delegates, callbacks, child → parent
unowned
  • Non-optional — dangling pointer if wrong
  • Only when lifetime is provably nested (expert-only)
05

Closures

Q5
@escaping vs synchronous use
func load(completion: @escaping (Result<Data, Error>) -> Void) {
    URLSession.shared.dataTask(with: url) { data, _, err in
        // completion invoked later → must be @escaping
        completion(.success(data ?? Data()))
    }.resume()
}

func map(_ x: Int, _ f: (Int) -> Int) -> Int { f(x) } // f is non-escaping
@escaping
  • Closure may outlive the function — stored or async
  • Self captures need [weak self] vigilance
Trailing closure
  • Last closure arg can trail outside parens
  • Multiple trailing closures (SwiftUI-style) — label clarity matters
06

Errors — throws & Result

Q6
throws + Result
enum LoadError: Error { case offline, badData }

func load() throws -> String {
    guard isOnline else { throw LoadError.offline }
    return try parse(raw)
}

// Result — async boundaries, Combine, callbacks
let r: Result<User, Error> = .failure(LoadError.badData)
throws
  • Typed errors with enums conforming to Error
  • Callers use try / try? / try!
Result
  • Completion handlers and async boundaries
  • map / flatMap without throwing through the whole stack
07

Property wrappers

Q7
@propertyWrapper + projection
@propertyWrapper
struct Clamped {
    private var value: Int
    let range: ClosedRange<Int>
    init(wrappedValue: Int, _ range: ClosedRange<Int>) {
        self.range = range
        self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
    }
    var wrappedValue: Int {
        get { value }
        set { value = min(max(newValue, range.lowerBound), range.upperBound) }
    }
}

struct Form {
    @Clamped(0...100) var progress = 0
    // $progress — projected value (wrapper-specific)
}
Common built-ins
  • @State, @Binding, @Published — know what each projects
  • $name — projected value (wrapper-specific)
Interview angle
  • Wrappers synthesize boilerplate (get/set, observation)
  • Custom wrappers: init, wrappedValue, sometimes projectedValue
08

Copy-on-write & collections

Q8
Array — shared buffer until mutation
var a = [1, 2, 3]
var b = a          // shares buffer until one mutates
b.append(4)          // copy now if needed
// Element type matters: [UIImage] copies references, not bitmaps
!
COW caveat: Collections copy cheaply, but elements that are reference types still share instances — mutating one element’s class state affects all “copies” holding that reference.
09

Sendable & isolation (language level)

Q9
@Sendable + key paths
// Sendable — cross-actor / concurrency boundaries
func runLater(_ work: @Sendable @escaping () -> Void) { }

struct Person: Sendable { let name: String; let age: Int }
let kp = \Person.age
people.sorted { $0[keyPath: kp] < $1[keyPath: kp] }
Sendable
  • Marks types safe to share across concurrency domains
  • Closures crossing actors often need @Sendable
mutating
  • Struct methods that mutate self
  • Value semantics — each mutation conceptually affects that binding
10

Quick comparison tables

Q1–Q10
TopicDefault / rule of thumbGotcha
struct vs classstruct for value data; class for identityCOW collections don’t fix shared class instances
Optionalguard for early exit; ?? for defaultsOptional chaining stops at first nil
weak vs unowneddefault to weakunowned if you’re wrong → crash
@escapinganything stored or called latercapture list to break cycles
Swift language — interview prep cheat sheetAligned with practical question set