HireHireInterview Quizzes › iOS Developer

iOS Developer Interview Questions

Think you're ready? These are the questions that actually decide iOS Developer interviews. Warm up on Easy — then face the Hard round, where 95% of candidates crumble. 30 questions across 3 levels, instant score, completely free.

30Questions
3Difficulty levels
95%Fail the hard round
FreeInstant score
Easy
Warm-up · 10 Qs
Medium
Practical · 10 Qs
Hard
Brutal · 10 Qs
⚡ Take the iOS Developer quiz — get your score →

The iOS Developer interview questions

Below are the real questions, grouped by difficulty. Expand any one to reveal the correct answer and why — or take the timed quiz for a score you can share. Can you clear the Hard round?

Easy round 10 questions

In Swift, what is the key difference between a `struct` and a `class`?
  • A. Structs are reference types; classes are value types
  • B. Structs are value types (copied on assignment); classes are reference types (shared by reference) ✓
  • C. Only classes can have properties and methods
  • D. Structs cannot be used with protocols
Correct answer: B. Structs are value types that are copied on assignment, while classes are reference types that share the same instance via references.
What does the `weak` keyword primarily help prevent in Swift?
  • A. Retain cycles (memory leaks) between objects with strong references ✓
  • B. Race conditions on background threads
  • C. Force-unwrap crashes on optionals
  • D. Slow app launch times
Correct answer: A. A `weak` reference does not increase the retain count, breaking strong reference cycles that would otherwise cause memory leaks.
Which statement about Automatic Reference Counting (ARC) in iOS is correct?
  • A. ARC runs a background garbage collector periodically to free memory
  • B. ARC deallocates an object when its strong reference count reaches zero ✓
  • C. ARC only manages memory for value types like structs and enums
  • D. ARC requires manually calling retain and release in Swift
Correct answer: B. ARC frees an object's memory as soon as the number of strong references to it drops to zero, without a periodic garbage collector.
You must update a UILabel's text after a network call finishes on a background thread. What is the correct approach?
  • A. Update the label directly from the background thread for speed
  • B. Dispatch the UI update to the main thread using DispatchQueue.main.async ✓
  • C. Use DispatchQueue.global().async to update the label
  • D. Wrap the update in a background Operation with high priority
Correct answer: B. All UIKit UI updates must occur on the main thread, so the update must be dispatched via DispatchQueue.main.async.
In Swift, what does the optional chaining expression `user?.address?.city` evaluate to if `address` is nil?
  • A. It throws a runtime error
  • B. It returns nil without crashing ✓
  • C. It returns an empty string
  • D. It force-unwraps and crashes
Correct answer: B. Optional chaining short-circuits and returns nil safely whenever any link in the chain is nil, avoiding a crash.
In the SwiftUI lifecycle, what does the `@State` property wrapper do?
  • A. Stores a source-of-truth value owned by the view that triggers a re-render when it changes ✓
  • B. Injects a shared object from the environment into the view
  • C. Creates a two-way binding to a value owned by a parent view
  • D. Persists the value permanently to UserDefaults
Correct answer: A. `@State` declares view-owned mutable state, and SwiftUI re-renders the view's body whenever that state changes.
Which HTTP-related class is the modern, recommended API for making network requests natively in iOS?
  • A. NSURLConnection
  • B. URLSession ✓
  • C. CFNetwork sockets directly
  • D. UIWebView
Correct answer: B. URLSession is Apple's current native networking API; NSURLConnection is deprecated and UIWebView is removed.
What is the purpose of the `Codable` protocol in Swift?
  • A. To make a type thread-safe automatically
  • B. To enable encoding and decoding a type to and from formats like JSON ✓
  • C. To automatically persist a type to Core Data
  • D. To compress the object in memory
Correct answer: B. `Codable` (Encodable & Decodable) lets a type be serialized to and deserialized from external representations such as JSON.
In a UITableView with the standard cell-reuse pattern, why do you call `dequeueReusableCell(withIdentifier:)`?
  • A. To create a brand-new cell for every visible row
  • B. To reuse off-screen cells and avoid allocating a cell per row for performance ✓
  • C. To sort the table's data source automatically
  • D. To cache network images in the cell
Correct answer: B. Dequeuing reuses recycled off-screen cells instead of instantiating a new cell for each row, keeping scrolling smooth and memory low.
What is the difference between `DispatchQueue.main.async` and `DispatchQueue.main.sync`?
  • A. `async` blocks the caller until the work completes; `sync` returns immediately
  • B. `sync` blocks the current thread until the submitted work finishes; `async` returns immediately ✓
  • C. Both block the caller identically
  • D. `async` can only run on background queues
Correct answer: B. `sync` blocks the calling thread until the submitted block completes, whereas `async` schedules the work and returns immediately.

Medium round 10 questions

You have a closure that captures self and is stored as a property on a view controller, creating a retain cycle. What is the standard way to break it?
  • A. Mark the closure as @escaping
  • B. Use a capture list with [weak self] ✓
  • C. Declare the closure property with lazy
  • D. Call the closure on a background queue
Correct answer: B. A [weak self] capture list stops the closure from strongly retaining the view controller, breaking the retain cycle.
In Swift, what is the difference between a struct and a class that most affects how instances are passed around?
  • A. Structs are passed by reference, classes by value
  • B. Structs are value types (copied on assignment), classes are reference types (shared) ✓
  • C. Structs cannot have methods, classes can
  • D. Structs are stored on the heap, classes on the stack
Correct answer: B. Structs are value types and get copied when assigned or passed, while classes are reference types where instances share the same underlying object.
You need to update a UILabel's text after fetching data from a network request. On which queue must the UI update run?
  • A. Any global concurrent queue
  • B. The main queue (DispatchQueue.main) ✓
  • C. A custom serial background queue
  • D. The URLSession delegate queue
Correct answer: B. All UIKit UI updates must happen on the main queue, so you dispatch back to DispatchQueue.main after the background network work completes.
In a UITableView with many rows, why do you call dequeueReusableCell(withIdentifier:) instead of creating a new cell each time?
  • A. It is required for the cell to respond to taps
  • B. It recycles off-screen cells to reduce memory use and improve scrolling performance ✓
  • C. It automatically sorts the cells alphabetically
  • D. It is the only way to set a cell's height
Correct answer: B. Cell reuse recycles cells that scroll off-screen so the table only allocates a small number of cells regardless of row count, keeping scrolling smooth.
Given `let name: String?`, which is the safest way to use the value only when it is non-nil?
  • A. if let unwrapped = name { ... } ✓
  • B. let unwrapped = name!
  • C. let unwrapped = name as! String
  • D. let unwrapped = name ?? name
Correct answer: A. Optional binding with `if let` safely unwraps the value only when it exists, avoiding the crash that `!` or a forced cast would cause on nil.
In SwiftUI, you have a value that a view needs to both read and mutate, and changes should re-render the view. Which property wrapper do you use for a simple local source of truth?
  • A. @Binding
  • B. @State ✓
  • C. @Environment
  • D. @Published
Correct answer: B. @State creates a local, view-owned source of truth that triggers a re-render when its value changes; @Binding is for passing that state down to child views.
Your app crashes with EXC_BAD_ACCESS when a delegate is called after its owner is deallocated. How should the delegate property typically be declared to avoid this class of problem?
  • A. As a strong var so it is never released
  • B. As a weak var to avoid retain cycles and dangling ownership ✓
  • C. As a lazy var initialized on first use
  • D. As a computed property returning self
Correct answer: B. Delegates are conventionally declared weak to prevent retain cycles between the delegating object and its delegate.
You call a function that is marked `async`. What must you do to call it from within another function?
  • A. Wrap the call in a DispatchQueue.main.sync block
  • B. Prefix the call with `await` inside an async context or Task ✓
  • C. Add a completion handler parameter to the call
  • D. Mark the calling function @objc
Correct answer: B. An async function must be awaited, and that await must occur inside another async function or a Task.
In Auto Layout, you set a UILabel's leading, trailing, and top constraints but the label collapses to zero height. What is the most likely fix?
  • A. Add a bottom or height constraint so its vertical position is fully defined ✓
  • B. Set translatesAutoresizingMaskIntoConstraints to true
  • C. Increase the label's content hugging priority to 1000
  • D. Remove the leading and trailing constraints
Correct answer: A. The vertical axis is under-constrained; adding a bottom or explicit height constraint gives the layout enough information to size the label.
You want to persist a small dictionary of user preferences like a theme choice and a boolean flag. Which storage mechanism is the most appropriate?
  • A. Core Data
  • B. UserDefaults ✓
  • C. A SQLite database via FMDB
  • D. Keychain Services
Correct answer: B. UserDefaults is designed for small amounts of lightweight preference data like flags and simple settings.

Hard round 10 questions

An actor caches a computed value: `func total() async -> Int { if let c = cache { return c }; let v = await recompute(); cache = v; return v }`. Under concurrent callers, what correctness problem exists?
  • A. None — actor isolation serializes all access, so `cache` can never be read stale
  • B. During the `await recompute()` suspension another task can enter the actor and also see `cache == nil`, causing redundant recomputes and a possible last-writer-wins overwrite ✓
  • C. The compiler rejects it because `cache` is mutated inside an async method on an actor
  • D. `recompute()` runs on a background thread, so `cache = v` is a data race the runtime will trap on
Correct answer: B. Actors are reentrant: at an `await` suspension point another task can run actor code, so the pre-await `cache == nil` check no longer holds and the invariant breaks.
A `struct Container { var items: [Int] }` uses copy-on-write via `Array`. You hold `var a = Container(...)`, do `let b = a`, then `a.items.append(1)`. What determines whether the append copies the backing buffer?
  • A. It always copies because `let b = a` copies the whole struct including the array buffer eagerly
  • B. It copies only if the `Array`'s storage is not uniquely referenced at mutation time — since `b` shares the buffer, `isKnownUniquelyReferenced` is false and it copies ✓
  • C. It never copies because `a` and `b` are separate struct instances with independent buffers
  • D. It copies only if `Container` is marked `@frozen`, otherwise the buffer is shared forever
Correct answer: B. COW defers duplication until mutation and copies only when the buffer's reference count shows it is shared, which it is because `b` still points at the same storage.
In SwiftUI, a parent view creates a child as `ChildView(model: MyModel())` where `ChildView` declares `@StateObject private var model`. The parent re-renders frequently. What happens to the `MyModel` instance?
  • A. A fresh `MyModel` is created and adopted on every parent re-render, resetting the child's state
  • B. The `MyModel()` in the initializer is evaluated on every re-render but `@StateObject` keeps the first instance and discards the newly created ones ✓
  • C. `@StateObject` forbids passing an instance through the initializer; this fails to compile
  • D. The child observes the parent's model by reference, so parent re-renders mutate it in place
Correct answer: B. `@StateObject`'s autoclosure runs each time the initializer is called but SwiftUI only uses the value from first appearance and keeps that instance stable, so the extra allocations are wasted but state is preserved.
Swift 6 flags: `final class Cache { var data: [String:Int] = [:] }` passed into `Task.detached { cache.data["x"] = 1 }`. Why the data-race diagnostic, and the correct minimal fix?
  • A. `Cache` isn't `Sendable` because it has mutable non-isolated state; make it an `actor` (or add a lock and mark it `@unchecked Sendable`) ✓
  • B. Add `@Sendable` to the closure — that alone makes the capture safe
  • C. Mark `Cache` as `final` — it already is, so the error is a compiler bug you silence with `nonisolated`
  • D. Change `var data` to `let data` — an immutable dictionary reference makes concurrent writes safe
Correct answer: A. A final class with mutable state is not `Sendable`; making it an actor gives isolated serialized access, whereas `@Sendable` on the closure or `let` on the reference does not protect the dictionary's contents from concurrent writes.
Given `protocol P { func f() -> String }` with `extension P { func f() -> String { "ext" }; func g() -> String { "g-" + f() } }` and `struct S: P { func f() -> String { "S" } }`. What does `(S() as P).g()` return?
  • A. "g-ext" because `g` is only in the extension and calls the extension's `f`
  • B. "g-S" because `f` is a protocol requirement, so it dispatches through the witness table to `S.f` ✓
  • C. "g-ext" because calling through the existential `as P` erases the concrete type and uses defaults
  • D. It is ambiguous and fails to compile due to two candidate `f` implementations
Correct answer: B. Because `f()` is declared in the protocol it is a requirement dispatched dynamically via the witness table to `S`'s override, so `g` (even from the extension) sees "S".
You register `NotificationCenter.default.addObserver(forName:object:queue:using:)` with a closure capturing `self`, in a UIViewController. What is the leak/behavior risk and best remedy?
  • A. No risk — NotificationCenter uses weak references to observers automatically
  • B. The block-based API retains the closure (and thus `self`); use `[weak self]` and/or remove the observer token in `deinit` ✓
  • C. The controller leaks only if the notification never fires; firing it releases the closure
  • D. Using `queue: .main` breaks the retain cycle by hopping threads
Correct answer: B. The block-based observer API strongly retains the closure until you remove the returned token, so capturing `self` strongly keeps the controller alive; capture weakly and remove the token.
A `Timer.scheduledTimer(withTimeInterval:repeats:true)` block captures `self` (a view controller). Even after the VC is dismissed it never deinits. Why, and which fix actually works?
  • A. The run loop weakly holds the timer; add `[weak self]` and it will deinit immediately on dismiss
  • B. The run loop retains the repeating timer, and the timer retains the closure retaining `self`; you must invalidate the timer (weak self alone won't stop the timer or free it) ✓
  • C. `Timer` is `Sendable`, so it must be recreated on the main actor to break the cycle
  • D. Setting `timer.tolerance` releases the closure between fires, breaking the retain cycle
Correct answer: B. The run loop keeps the repeating timer alive and the timer strongly holds its closure; `[weak self]` avoids the VC being kept alive but you still must `invalidate()` to stop and release the timer.
Comparing `func makeShape() -> some Shape` (opaque) vs `func makeShape() -> any Shape` (existential) for a factory returning one concrete `Circle`. Which statement is correct?
  • A. `some Shape` erases the type at runtime and boxes it, while `any Shape` preserves the static type with no allocation
  • B. `some Shape` pins one hidden concrete type known to the compiler (enabling static dispatch/specialization), while `any Shape` is a runtime box allowing heterogeneous returns at a dynamic-dispatch cost ✓
  • C. They are interchangeable; `some` and `any` compile to identical code for a single concrete return
  • D. `any Shape` cannot be returned from a function that has only one concrete return type
Correct answer: B. `some` guarantees one specific underlying type the compiler tracks (permitting specialization), whereas `any` is an existential box that supports differing dynamic types at the cost of indirection.
Two serial queues A and B. Code on A does `B.sync { ... }` while code on B does `A.sync { ... }` concurrently. What is the outcome, and what is the classic single-queue variant of this bug?
  • A. A priority inversion that Instruments flags as a Hitch; the single-queue variant is calling `async` on the same queue
  • B. A deadlock from cyclic `sync` waits; the single-queue variant is calling `queue.sync` from a block already running on that same serial queue ✓
  • C. A livelock that resolves once the queues drain; the single-queue variant is nesting `autoreleasepool`
  • D. Nothing — `DispatchQueue.sync` is reentrant on serial queues, so both complete
Correct answer: B. Mutual `sync` waits create a cyclic dependency deadlock, and the well-known single-queue form is `sync`-ing onto the very serial queue you're already executing on.
You call `context.perform` correctly for a Core Data background `NSManagedObjectContext`, fetch objects, then pass those `NSManagedObject` instances to the main thread to display. What rule is violated?
  • A. None — objects fetched inside `perform` are thread-safe to read anywhere afterward
  • B. Managed objects are bound to their context's queue; passing the objects across threads violates the threading contract — pass `NSManagedObjectID` and re-fetch on the main context instead ✓
  • C. You must call `context.reset()` before crossing threads, otherwise faults leak
  • D. Only saving across threads is unsafe; reading properties of a managed object from any thread is allowed
Correct answer: B. Managed objects are not thread-safe and belong to their context's queue; the safe hand-off is the `objectID`, re-fetched (or `object(with:)`) on the destination context.

Prep for another role

Questions are original, written and independently verified for HireHire's role interview quizzes. They reflect the kind of knowledge iOS Developer interviews test, not any specific company's questions. HireHire maps live tech & IT jobs across India, updated regularly. Last updated: July 2026.