Interview track

iOS Developer interview prep

A spaced-repetition deck of 153+ iOS Developer interview questions — organised by topic and difficulty, and scheduled for review based on your ratings. Preview a few cards below, then choose access to study the whole track on an Anki-style SM-2 schedule.

153 cards10 topics
See access options

Eligible new monthly or yearly subscribers get 7 days free. Checkout confirms eligibility and charges.

Before you start

Start with basic Swift and experience building a small iOS app. Shared algorithms include Python examples; the concepts transfer, but practising them in Swift is a separate step.

First pass: Swift & the Type System → Memory & ARC. Begin with the foundational questions in these modules, then follow the outline. Revisit advanced and senior questions as the job requires; all cards remain available from the start.

Shared topics can appear in several tracks and keep one review history. The outline labels shared foundations and role-specific modules; choose your depth for the job you are preparing for.

What's covered

Every topic in this track, grouped the way you'd study it.

Swift & the Type System

12 cards

Role-specific module

Swift & Types

Memory & ARC

9 cards

Role-specific module

Memory & ARC

Concurrency: GCD & Swift Concurrency

11 cards

Role-specific module

GCD & Swift Concurrency

UIKit & Layout

10 cards

Role-specific module

UIKit & Layout

SwiftUI

8 cards

Role-specific module

SwiftUI

iOS App Architecture

8 cards

Role-specific module

Architecture

Networking & Persistence

9 cards

Role-specific module

Networking & Persistence

Performance & Tooling

8 cards

Role-specific module

Performance & Tooling

CS Fundamentals

43 cards

Shared foundations

Data Structures & Algorithms

Behavioral

35 cards

Shared foundations

Behavioral

Sample questions

A few cards from the deck — reveal each answer, then choose access to study the full set on a schedule.

What are the real differences between a struct and a class in Swift?

Short answer: A struct is a value type; a class is a reference type with identity. An ordinary copyable struct has copy semantics, while assigning a class reference shares the instance. Inheritance and === belong to classes. Since Swift 5.9, noncopyable ~Copyable structs can have deinit, but do not allow ordinary copying.

In depth:

struct class
Semantics a value; normally Copyable a shared reference
Inheritance no; protocols are supported yes
Identity === no yes
deinit for noncopyable structs yes
Mutation changing stored fields requires var and mutating methods var properties can change through a let reference
ARC may apply to reference fields manages instance lifetime
Initializer memberwise when synthesis conditions hold automatic default/inherited initializers only under specific conditions
  1. Structs suit independent values. Copies may be optimized, for example with copy-on-write; reference fields can still share mutable state.
  2. Classes suit shared identity or inheritance. Neither struct nor class automatically guarantees thread safety.
  3. A noncopyable resource can use struct Resource: ~Copyable { deinit {} }: ownership transfers without copying.

⚠️ Common mistake: “structs always live on the stack.” Value semantics do not determine memory placement: a struct can live inside a heap object or be boxed in an existential.

How does ARC work, and how is it different from a garbage collector?

Short answer: ARC manages strong references automatically: the compiler inserts and optimizes retain/release operations, which run during program execution. When the last strong reference is released, the object can be destroyed. ARC does not search for unreachable cycles, so strong cycles require explicit ownership fixes.

In depth:

ARC Tracing GC
Mechanism compiler-arranged strong-reference counting traversal of the reachable graph during collection
Reclamation on final release; its position depends on lifetime and optimization collector policy after unreachability is established
Latency retain/release and synchronous deinit cascades can take time implementation-dependent: pauses, incremental, or concurrent work
Cycles an unreachable strong cycle is not collected automatically unreachable cycles can be collected
  1. Lifetime — the last source line of a scope need not coincide with the final release. The optimizer may adjust lifetimes within language rules; use withExtendedLifetime when appropriate.
  2. Resources — deinit is useful for final cleanup, but explicit close/cancel/unsubscribe operations, with defer where suitable, better express required timing. A retain cycle may prevent deinit from running at all.
  3. Weak references — weak does not retain and becomes nil; unowned requires the object to remain alive whenever accessed. Choose from the ownership model.

⚠️ Common mistake: “ARC only works at compile time and has no pauses.” Reference counting and reclamation still cost runtime work, and an expensive deinit can delay a thread.

How does a serial queue differ from a concurrent one, what are main and global — and why is a queue not a thread?

Short answer: A queue is a "list of tasks" abstraction, not a thread: GCD multiplexes queues over a shared thread pool. A serial queue runs tasks strictly one at a time in FIFO order; a concurrent one runs several in parallel. Main is the only queue with the contract "I execute on the main thread"; global queues are system concurrent queues, one per QoS class.

In depth:

Queue Kind What's special
DispatchQueue.main serial bound to the main thread; all UI
DispatchQueue.global(qos:) concurrent system-owned, shared app-wide
DispatchQueue(label:) serial by default your own; attributes: .concurrent makes it parallel
QoS What it's for
.userInteractive animations, gesture response — "right now"
.userInitiated the user is waiting for a result (opening a screen)
.utility long work with progress (file download)
.background invisible work: sync, indexing
  1. Queue ≠ thread — a serial queue doesn't own a thread: its tasks may run on different pool threads, just never simultaneously.
  2. QoS drives scheduling — QoS tells the system how much CPU and energy to grant: .userInteractive jumps the line, .background can wait.

⚠️ Common mistake: "creating a queue creates a thread." No: thousands of queues are served by a dozen pool threads. But blocking many threads with sync waits is the road to thread explosion.

The UIViewController lifecycle: in what order are the methods called, and what belongs where?

Short answer: loadView creates the view → viewDidLoad — one-time setup (no geometry yet!) → viewWillAppear — refresh before every presentation → viewWillLayoutSubviews/viewDidLayoutSubviews — the only places where frames are valid → viewDidAppear — animations and expensive starts → viewWillDisappear/viewDidDisappear.

In depth:

loadView ─► viewDidLoad ─► viewWillAppear ─► viewWillLayoutSubviews
 (create     (once)         (every show)            │ layout pass
  view)                                             ▼
viewDidDisappear ◄─ viewWillDisappear ◄─ viewDidAppear ◄─ viewDidLayoutSubviews
                                          (on screen)      (frames are valid!)
  1. viewDidLoad — once per view controller's life: add subviews, constraints, subscriptions. The view has no real size yet.
  2. viewWillAppear — every presentation: refresh data, bar state.
  3. viewDidLayoutSubviews — bounds are current: corner rounding, CAShapeLayer paths, size-dependent layout. Called many times — keep it cheap.
  4. viewDidAppear — the view is on screen: start animations, expensive work, analytics.
  5. viewWillDisappear/viewDidDisappear — stop timers, save state.

⚠️ Common mistake: reading view.bounds in viewDidLoad — you get the storyboard size or zeros, not the device's real geometry. Anything size-dependent belongs in viewDidLayoutSubviews.

Why are SwiftUI Views structs, and why isn't constantly recreating them expensive?

Short answer: A View is not a view in the UIKit sense but a lightweight value-description: a blueprint of what to show. Structs are cheap to create and throw away, so SwiftUI freely recreates them on every body recomputation. The long-lived part — the render tree (attribute graph) — lives behind the scenes; that's where state and identity are kept.

In depth:

  1. Blueprint, not instance — a UIView is a heavyweight object with a lifecycle and a layer; a View struct is a couple of fields plus body, holding no resources.
  2. State lives outside — @State is stored by the framework and attached to the view's identity in the tree, not to the struct: the struct dies, the value survives.
  3. Diffing — SwiftUI compares the new description with the old one and minimally mutates the persistent tree; only the difference reaches the pixels.
body → View values (cheap, recreated every time)
            │ diff
            ▼
attribute graph / render tree (long-lived:
@State, identity, animations)

⚠️ Common mistake: equating "recreating structs" with "redrawing the UI." Producing descriptions costs pennies; rendering is what can be expensive, and it happens only where the diff found a change.

Why does Apple-style MVC turn into Massive View Controller, and how do you slim a VC without changing the architecture?

Short answer: Because in UIKit the view controller is the default assembly point for everything: view lifecycle, layout, the table's data source, navigation and network calls. You slim it by extracting responsibilities into separate objects — no architecture change required.

In depth:

  1. Diagnosis — Apple's MVC isn't bad per se; the problem is that the "C" happily accepts any code, and without discipline the view controller grows to thousands of lines.
  2. What to extract:
Responsibility Where it goes
UITableViewDataSource / delegate a dedicated data source object
A screen section with its own logic a child view controller
Networking, cache, analytics plain service objects
Subview configuration the views themselves (configure(with:))
  1. Result — the view controller stays a conductor: it wires services, views and navigation together but doesn't do all the work itself.

⚠️ Common mistake: answering "MVC is a bad architecture, that's why everyone moved to MVVM." What's bad is an undisciplined implementation: without extracting responsibilities the same overload happens to a ViewModel too.

Ready to make it stick?

Explore a sample, choose a track, and build a review routine that fits your preparation.

Questions about this track

How should I prepare for an iOS Developer interview?

Study the concepts you'll be asked to explain, not just the ones you can code. RecallDeck's iOS Developer track gives you 153+ curated interview questions and resurfaces each one with an Anki-style SM-2 schedule based on your ratings — to practise recalling and explaining them. Combine reviews with coding and mock interviews.

What topics does the iOS Developer track cover?

The iOS Developer track is organised into the core areas iOS Developer interviews actually test, grouped by topic and by difficulty (Concept, Junior, Middle, Senior). You can preview the full outline and sample questions above before signing in.

Is spaced repetition effective for iOS Developer interview prep?

Yes. Active recall and spaced practice can help retain what you study. Grade yourself honestly and check your understanding with practical work. RecallDeck schedules each iOS Developer card using your ratings, so familiar cards return less often and difficult ones get more practice.

Can I try the iOS Developer track before paying?

Eligible new monthly or yearly subscribers can try the complete iOS Developer track and every feature for seven days. Checkout confirms trial eligibility and the amount due; returning subscribers may be charged immediately. Cancel online before the trial ends to avoid its first charge. Lifetime access has no trial.

Other interview tracks