Skip to content
SwiftMind

Swift

4 interview questions on swift.

When is force-unwrapping with `!` a reasonable choice, and when is it a bug waiting to happen?

MidTap to think, then reveal

! is an assertion that a value cannot be absent. It's reasonable when absence would mean the program is already broken and you want to fail loudly and immediately: a resource you ship inside your own bundle, a storyboard outlet that must be connected, an invariant established two lines above. In those cases a crash on the developer's machine during the first run is *better* than silently degrading.

It's a bug waiting to happen the moment the value's absence depends on anything outside your control — a server response, user input, a file on disk, a cache that may have been evicted, a view that may not have loaded yet. Those aren't programmer errors, they're expected states, and they deserve guard let and a real fallback.

The test I apply: can I write down the invariant that guarantees this is non-nil, and is that invariant enforced by code I own? If not, don't force it.

What an interviewer listens for

  • Distinguishes programmer error from expected runtime absence
  • Names concrete legitimate uses rather than saying "never use it"
  • Recognises crashing early can be preferable to silent corruption
  • Mentions that data from outside the process is never a candidate

Follow-ups

How would you find risky force-unwraps in an existing codebase?

Start with the crash reports — "Unexpectedly found nil while unwrapping" tells you the exact line, and the frequency tells you which ones actually matter, which beats auditing every ! by hand. For prevention, a SwiftLint rule (force_unwrapping) can warn on new ones while leaving existing code alone, so the count only goes down. I'd also treat try! and as! the same way — same assertion, same failure mode.

Read the concept: Optionals

How do you decide between a struct and a class for a new type?

MidTap to think, then reveal

I start from struct and require a reason to move.

I move to a class when I need identity — when "is this the same object?" is a meaningful question distinct from "are these equal?". A user session, a network connection, a view controller: two of them with identical fields are still not interchangeable.

Also when I need shared mutable state that several owners must see change, when I need deinit to release something (an observer, a file handle, a socket), when I need inheritance or Objective-C interop, or when the object is genuinely long-lived and referenced from many places.

Everything else — models decoded from JSON, geometry, configuration, anything I want to pass across threads without worrying — is a struct. Value semantics mean I can hand it to another part of the app and know nobody can mutate it behind my back, which removes a whole category of bug rather than requiring me to defend against it.

I don't use size as a tiebreaker, because copy-on-write already handles large payloads.

What an interviewer listens for

  • Defaults to struct and justifies the exception rather than the rule
  • Names identity as the core distinction, not performance
  • Mentions deinit, inheritance and ObjC interop as concrete class-only needs
  • Knows copy-on-write removes the "too big to copy" argument
  • Connects value semantics to thread safety

Follow-ups

Where does mutating come into this?

A struct's methods can't change self unless marked mutating, and you can only call a mutating method on a variable, never a constant. It's the same value-semantics rule surfacing in the method list: mutating a value means replacing it, so the compiler needs somewhere writable to put the result. Protocol requirements need mutating too if any struct conformer will mutate.

Read the concept: Struct vs Class

A colleague adds `[weak self]` to every closure in the codebase "to be safe". What's your response?

SeniorTap to think, then reveal

It's cargo-culting a real rule, and it introduces a different class of bug.

[weak self] is the right tool when the closure is stored somewhere self can reach — a stored property, a subscription, a delegate-ish callback — because that's the shape that creates a cycle. It does nothing useful for a non-escaping closure like the one you pass to map or forEach: that closure cannot outlive the call, so there's no cycle to break, and you've added an optional unwrap and noise for nothing.

Worse, it's actively wrong for one-shot escaping work you *want* to complete. If a network completion handler weakly captures self and the object is deallocated mid-flight, the handler runs and silently does nothing — no save, no cleanup, no log. That's a bug that never crashes and never appears in a stack trace, so it's much harder to find than the leak it was meant to prevent.

So: reach for [weak self] when the closure's lifetime is owned by self, and think about whether the work needs to finish before you weaken the reference that keeps it alive.

What an interviewer listens for

  • Knows non-escaping closures can't create cycles, so weak there is pure noise
  • Identifies that weak self can silently skip work that needed to happen
  • Frames the decision around who owns the closure's lifetime
  • Doesn't reduce it to a blanket rule in either direction

Follow-ups

How would you decide between `weak` and `unowned` for a closure capture?

unowned when the closure genuinely cannot outlive the object — a closure stored on self and only invoked by self — because it avoids the optional and the crash is loud if you're wrong about the invariant. weak whenever the closure's lifetime is controlled by someone else: a network layer, a timer, a notification centre. When I'm not certain, weak: an unnecessary if let costs nothing, an incorrect unowned crashes on a user's phone.

Does `[weak self]` prevent all retain cycles in that closure?

No. It only changes how self is captured. Any other object the closure mentions is still captured strongly, so a closure holding a strong reference to a view model that holds the closure is still a cycle even with [weak self] present. And if the closure touches a property of self without going through the weak binding, you can end up capturing self anyway.

Read the concept: Closures

When would you reach for a protocol, and when is one actively making a codebase worse?

SeniorTap to think, then reveal

The case for a protocol is a *seam*: a point where I genuinely need to swap what's on the other side. Two real implementations, or one real implementation plus a test double that lets me test without a network, a database, or a clock. That's a protocol earning its keep — it turns an untestable dependency into an injectable one.

It makes things worse when it's speculative. A protocol with one conformer, added because it felt like good architecture, costs you a second file, an indirection that breaks jump-to-definition, and often a pile of any boxing. Nobody can see what the code actually does without opening two files, and the abstraction usually turns out to be the wrong shape when the second implementation finally arrives — because it was designed against a sample size of one.

The other failure mode is protocols that mirror a single type's entire API surface. That's not an abstraction, it's a duplicate declaration you now maintain twice. A good protocol is narrower than its conformers: ContentLoading with one method, not ContentLoaderProtocol with all fourteen.

Extracting a protocol later is a small, safe, mechanical refactor. Deleting one that's wired through forty files is not. So I let the second implementation pull it into existence.

What an interviewer listens for

  • Ties the decision to a real second implementation, counting test doubles
  • Names concrete costs: navigation, boxing, extra file, wrong-shape abstraction
  • Calls out protocols that merely mirror one type's full API
  • Observes that extraction later is cheaper than removal later
  • Prefers narrow protocols over broad ones

Follow-ups

How do protocols with associated types change the picture?

They stop being usable as simple existentials. any Collection is legal since Swift 5.7, but the moment you touch a member whose signature mentions Element, you need to open the existential — usually by passing it into a generic function. In practice PATs push you toward generics: func show<C: Collection>(_ items: C) rather than func show(_ items: any Collection). That's usually better anyway, since it keeps dispatch static.

How does this interact with SwiftUI's `some View`?

some View exists because a SwiftUI body's real type is an enormous nested generic — VStack<TupleView<(Text, Button<Text>)>> and worse. You can't write that down, and any View would box every view and destroy the static structure SwiftUI relies on for diffing. Opaque return types let the compiler keep the precise type while you write three words. It's the flagship use case that motivated the feature.

Read the concept: Protocols

Drill interview-level questions

Advanced and senior quiz questions only, across every category.

Start