Swift Basics
Optionals, `let`/`var`, enums, collections, `guard`/`defer`, and pattern matching — the language foundation every iOS interview starts from.
13 questions
JuniorTheoryVery commonHow does guard let differ from if let, and when does guard read better?
How does guard let differ from if let, and when does guard read better?
if let binds the unwrapped value only inside its own braces. guard let binds into the enclosing scope and forces its else to exit (return, throw, break). guard reads better for early-exit validation, keeping the happy path unindented.
Common mistakes
- ✗Thinking an
if letbinding is visible after theifblock ends - ✗Believing the
elsebranch ofguardis optional - ✗Assuming
guard letandif letscope the binding identically
Follow-up questions
- →Why must a
guardelsebranch transfer control out of the scope? - →How does
guard letkeep the happy path flat versus nestedif let?
JuniorTheoryVery commonWhat is an optional in Swift, and what class of bug does it eliminate at compile time?
What is an optional in Swift, and what class of bug does it eliminate at compile time?
An optional (Optional<Wrapped>, i.e. T?) either holds a value (.some) or is nil (.none). It makes absence explicit in the type system, so the compiler forces you to handle nil — eliminating unexpected null-dereference crashes.
Common mistakes
- ✗Thinking an optional auto-substitutes a default value instead of
nil - ✗Believing optionals apply only to reference or pointer types
- ✗Confusing an optional with Swift's
throwserror mechanism
Follow-up questions
- →How does
Optionaldesugar — what are.someand.none? - →Why does the compiler reject using a
T?where a plainTis expected?
JuniorPerformanceCommonWhat is the cost of inserting an element at the start of a Swift Array, and why?
What is the cost of inserting an element at the start of a Swift Array, and why?
Inserting at index 0 is O(n) — a Swift Array stores elements contiguously, so every existing element shifts one slot right. Appending at the end is amortized O(1). For frequent front insertion, use another structure or build reversed.
Common mistakes
- ✗Assuming prepend is as cheap as append on an
Array - ✗Thinking
Arrayuses a linked structure that avoids shifting - ✗Attributing the cost to reallocation rather than element shifting
Follow-up questions
- →Why is
appendonly amortized O(1) rather than always O(1)? - →What data structure would you reach for if you prepend often?
JuniorTheoryCommonArray vs Set vs Dictionary — compare lookup cost and ordering, and what must a Set require of its element?
Array vs Set vs Dictionary — compare lookup cost and ordering, and what must a Set require of its element?
Array is index-ordered — index access O(1), membership search O(n). Set and Dictionary are unordered hash tables with average O(1) lookup. A Set requires its elements (a Dictionary its keys) to be Hashable, so equal values hash alike.
Common mistakes
- ✗Assuming
Arraymembership lookup is O(1) like aSet - ✗Thinking a
SetorDictionarypreserves insertion order - ✗Believing
SetneedsComparableorEquatablerather thanHashable
Follow-up questions
- →Why does hashing give a
Setaverage O(1) rather than guaranteed O(1)? - →What breaks if a type's
HashableandEquatableconformances disagree?
JuniorCodeCommonPredict the console output of a function with two defer blocks before its return.
Predict the console output of a function with two defer blocks before its return.
defer blocks run as the scope exits — here at return — in LIFO order, so the last-registered runs first. It prints body, then B, then A; the returned 42 prints last, after load() returns. Output — body, B, A, 42.
Common mistakes
- ✗Expecting
deferblocks to run in written (FIFO) order rather than LIFO - ✗Thinking a
deferfires at its own line rather than at scope exit - ✗Believing a normal
returnskips thedeferblocks
Follow-up questions
- →In what order do three
deferblocks in the same scope execute? - →Does a
deferstill run if the function exits by throwing an error?
JuniorTheoryCommonRaw values vs associated values in a Swift enum — what can each hold, and which gives init?(rawValue:) for free?
Raw values vs associated values in a Swift enum — what can each hold, and which gives init?(rawValue:) for free?
A raw value is one compile-time constant per case, all of the same backing type (Int, String, …), so the compiler synthesizes a failable init?(rawValue:). An associated value attaches different runtime data per case. A case has one, not both.
Common mistakes
- ✗Swapping which kind holds constants versus runtime data
- ✗Thinking a single case can carry both a raw value and associated values
- ✗Believing every enum gets
init?(rawValue:), not only raw-valued ones
Follow-up questions
- →Why is
init?(rawValue:)failable rather than a plaininit? - →What backing types can an enum's raw value legally be?
JuniorTheoryCommonWhat is an implicitly unwrapped optional, why does @IBOutlet use one, and when does it crash?
What is an implicitly unwrapped optional, why does @IBOutlet use one, and when does it crash?
An implicitly unwrapped optional (T!) is force-unwrapped automatically on every access. @IBOutlet uses one because the view is nil at init but connected before the view loads. It crashes if you access it while still nil, e.g. before viewDidLoad.
Common mistakes
- ✗Thinking an IUO returns
nilinstead of trapping when accessed empty - ✗Believing
T!is a non-optional with no crash risk - ✗Confusing an implicitly unwrapped optional with a
lazyproperty
Follow-up questions
- →Why is an outlet
nilbetweeninitandviewDidLoad? - →When would you replace an
@IBOutletIUO with a regular optional?
JuniorTheoryCommonWhat does let vs var guarantee, and why does a let struct differ from a let class reference?
What does let vs var guarantee, and why does a let struct differ from a let class reference?
let is a constant, var a mutable variable. On a value-type struct, let freezes the whole value — you can't reassign or mutate a stored property. On a reference-type class, let fixes only the reference; the object stays mutable.
Common mistakes
- ✗Thinking
leton a class instance prevents mutating the object's properties - ✗Believing
letvsvaris a performance choice rather than a mutability contract - ✗Assuming a
letstruct behaves like aletclass with reference-only immutability
Follow-up questions
- →Why can you call a mutating method through a
varstruct but not aletstruct? - →What does
letfreeze when the struct holds a class reference as a property?
JuniorTheoryCommonList the ways to unwrap an optional in Swift and rank them by safety.
List the ways to unwrap an optional in Swift and rank them by safety.
Safe — if let/guard let bind locally, ?? gives a default, ?. chains to nil on any nil link, case let destructures. Unsafe — force-unwrap ! traps on nil. Prefer the binding forms; reserve ! for provably non-nil values.
Common mistakes
- ✗Believing
?.orif letcan crash the way force-unwrap!does - ✗Ranking force-unwrap
!as safe because it is concise - ✗Forgetting that
??and pattern matching are also unwrapping tools
Follow-up questions
- →When is force-unwrap
!a defensible choice over a binding form? - →How does
?.differ fromif letwhen several links can benil?
MiddleCodeCommonSwitch over an enum with associated values using value binding and a where clause
Switch over an enum with associated values using value binding and a where clause
Match each case with value binding — case let .deposit(amount) — and add a where guard for a refined branch, e.g. case let .deposit(a) where a > 1000. Since the enum is finite, an exhaustive switch needs no default and flags any unhandled new case.
Common mistakes
- ✗Thinking associated values cannot be bound directly in the
casepattern - ✗Believing an exhaustive enum
switchstill needs adefault - ✗Misplacing the
whereclause or treating it as a loop
Follow-up questions
- →What does the compiler do when you add a case to an exhaustively-switched enum?
- →How does
case letdiffer from binding each associated value individually?
MiddleTheoryCommonIn a?.b?.c ?? d, walk through the evaluation — where does chaining stop, and where can this still crash?
In a?.b?.c ?? d, walk through the evaluation — where does chaining stop, and where can this still crash?
Optional chaining evaluates left to right and short-circuits — if a is nil, all of a?.b?.c is nil without touching b/c. The result is optional, so ?? d supplies d when nil. The chain can't crash, but d or a force-unwrap inside can.
Common mistakes
- ✗Thinking a nil link mid-chain crashes rather than short-circuiting to
nil - ✗Believing
?? dcatches force-unwrap or subscript crashes inside the chain - ✗Assuming the chain evaluates every link regardless of an earlier
nil
Follow-up questions
- →What is the static type of
a?.b?.cbefore the?? dis applied? - →How would adding a
!or[i]subscript inside the chain reintroduce a crash?
JuniorTheoryOccasionalWhy does a recursive enum need the indirect keyword in Swift?
Why does a recursive enum need the indirect keyword in Swift?
An enum is a value type stored inline, so a case holding the enum's own type would need infinite size. indirect stores that case's value behind a pointer (a heap box), not inline, breaking the size recursion. You mark one case or the whole enum indirect.
Common mistakes
- ✗Thinking a recursive enum compiles without
indirect - ✗Believing
indirectturns the enum into a reference type with shared storage - ✗Assuming
indirectis a performance flag rather than a layout requirement
Follow-up questions
- →What is the runtime cost of an
indirectcase? - →Can you mark just one case
indirectinstead of the whole enum?
JuniorTheoryOccasionalWhat problems do typealias and tuples solve, and what can a tuple NOT do that a struct can?
What problems do typealias and tuples solve, and what can a tuple NOT do that a struct can?
typealias gives an existing type a readable second name — it creates no new type. A tuple groups a few values into a lightweight, unnamed ad-hoc type. Unlike a struct, a tuple can't conform to protocols, hold methods, or be extended.
Common mistakes
- ✗Thinking
typealiasintroduces a new, distinct type - ✗Believing a tuple can conform to a protocol or gain methods
- ✗Assuming tuples and structs are fully interchangeable
Follow-up questions
- →Why can two
typealiasnames for the same type be used interchangeably? - →When does reaching for a
structbeat returning a tuple?