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. 80 questions across 3 levels, instant score, completely free.

80Questions
3Difficulty levels
95%Fail the hard round
FreeInstant score
Easy
Warm-up · 20 Qs
Medium
Practical · 30 Qs
Hard
Brutal · 30 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 20 questions

Given `var name: String? = nil` and then `print(name!)`, what happens?
  • A. Prints "nil"
  • B. Prints an empty string
  • C. Crashes at runtime ✓
  • D. Fails to compile
Correct answer: C. Force-unwrapping a nil optional triggers a runtime crash.
You write `let count = 5` and later `count = 6`. What is the result?
  • A. count becomes 6 at runtime
  • B. Compile-time error ✓
  • C. Runtime crash
  • D. count silently stays 5
Correct answer: B. A let constant cannot be reassigned, so this fails to compile.
Two class instances each hold a strong reference to the other. How do you prevent the resulting memory leak?
  • A. Convert them to structs
  • B. Mark one reference weak or unowned ✓
  • C. Call deinit manually
  • D. Declare both with let
Correct answer: B. Making one side weak/unowned breaks the strong reference cycle so ARC can deallocate them.
Given `struct P { var x = 0 }; var a = P(); var b = a; b.x = 5; print(a.x)`, what prints?
  • A. 0 ✓
  • B. 5
  • C. nil
  • D. Compile error
Correct answer: A. Structs are value types, so b is a copy and mutating it leaves a unchanged.
A network callback runs on a background thread and needs to update a UILabel's text. What must you do?
  • A. Update it directly; UIKit is thread-safe
  • B. Dispatch the update to DispatchQueue.main ✓
  • C. Wrap it in a Timer
  • D. Mark the label as @objc
Correct answer: B. UIKit updates must happen on the main thread, so you dispatch back to the main queue.
In `guard let x = value else { ... }`, what must the else block always do?
  • A. Assign a default to x
  • B. Exit the current scope (return, break, throw, etc.) ✓
  • C. Call the guard again
  • D. Print an error
Correct answer: B. guard requires the else branch to transfer control out of the scope so the unwrapped value stays valid afterward.
Delegate properties are conventionally declared `weak`. Why?
  • A. To make them faster to access
  • B. To avoid a strong reference cycle between the two objects ✓
  • C. Because protocols cannot be strong
  • D. To allow multiple delegates
Correct answer: B. A weak delegate prevents a retain cycle between the delegating object and its delegate.
Inside a closure you reference `self` and Xcode warns about a retain cycle. What is the common fix?
  • A. Capture self with [weak self] ✓
  • B. Remove the closure
  • C. Mark self as lazy
  • D. Make the closure @escaping
Correct answer: A. Capturing [weak self] avoids the closure strongly retaining self and creating a cycle.
Given `let arr = [1, 2, 3]` and then `print(arr[5])`, what happens?
  • A. Prints nil
  • B. Prints 0
  • C. Crashes with an index out of range error ✓
  • D. Returns an empty array
Correct answer: C. Accessing an index beyond the array's bounds triggers a fatal runtime error.
What does the `??` operator do in `let name = dict["key"] ?? "Guest"`?
  • A. Force-unwraps the optional
  • B. Provides "Guest" when the lookup is nil ✓
  • C. Throws if the key is missing
  • D. Compares two strings
Correct answer: B. The nil-coalescing operator supplies the right-hand default when the left side is nil.
In SwiftUI, you change a property marked `@State`. What does the framework do?
  • A. Nothing until you call reload
  • B. Re-computes the view's body and updates the UI ✓
  • C. Crashes if on a background thread
  • D. Persists it to disk automatically
Correct answer: B. Mutating @State invalidates the view and SwiftUI re-renders its body.
For a single UIViewController, which lifecycle method runs first?
  • A. viewWillAppear
  • B. viewDidLoad ✓
  • C. viewDidAppear
  • D. viewDidLayoutSubviews
Correct answer: B. viewDidLoad runs once when the view is loaded into memory, before any appearance callbacks.
What is the difference between `DispatchQueue.global().async` and `.sync`?
  • A. async blocks the caller; sync does not
  • B. async returns immediately; sync blocks until the work finishes ✓
  • C. They are identical
  • D. sync always runs on the main thread
Correct answer: B. async schedules work and returns right away, while sync blocks the caller until the block completes.
To decode a JSON response into your model type using JSONDecoder, your type must conform to which protocol?
  • A. Encodable
  • B. Decodable ✓
  • C. Hashable
  • D. Identifiable
Correct answer: B. JSONDecoder requires the target type to conform to Decodable.
If `user` is nil, what does `user?.profile?.name` evaluate to?
  • A. A runtime crash
  • B. nil, with no crash ✓
  • C. An empty string
  • D. A compile error
Correct answer: B. Optional chaining short-circuits to nil when any link is nil, safely avoiding a crash.
You want to compare two of your custom `struct` values with `==`. What must the struct do?
  • A. Override isEqual
  • B. Conform to Equatable ✓
  • C. Be a class instead
  • D. Implement Comparable
Correct answer: B. The == operator on a custom struct requires conformance to Equatable (which Swift can synthesize).
You declare a stored property with `weak var`. What must its type be?
  • A. A non-optional value type
  • B. An optional reference type ✓
  • C. A String
  • D. A struct
Correct answer: B. A weak reference can become nil when its object is deallocated, so it must be an optional class type.
An enum case `case error(code: Int)` is an example of what Swift enum feature?
  • A. Raw value
  • B. Associated value ✓
  • C. Computed property
  • D. Type erasure
Correct answer: B. Attaching data like `code: Int` to a case uses an associated value.
You have `let nums = [1, 2, 3]` and want `[2, 4, 6]`. Which is correct?
  • A. nums.filter { $0 * 2 }
  • B. nums.map { $0 * 2 } ✓
  • C. nums.reduce { $0 * 2 }
  • D. nums.forEach { $0 * 2 }
Correct answer: B. map transforms each element and returns a new array of the results.
In a UIKit app, which method is a good place to pause ongoing tasks when the app moves out of the foreground?
  • A. viewDidLoad
  • B. applicationDidBecomeActive
  • C. applicationDidEnterBackground ✓
  • D. application(_:didFinishLaunchingWithOptions:)
Correct answer: C. applicationDidEnterBackground is called when the app enters the background, the right spot to pause work.

Medium round 30 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.
In Swift ARC, which reference type does NOT increase retain count, becomes nil on deallocation, and helps break retain cycles?
  • A. strong
  • B. weak ✓
  • C. unowned
  • D. lazy
Correct answer: B. A weak reference does not increase retain count and automatically becomes nil when the referent is deallocated.
What is the difference between 'weak' and 'unowned' references in Swift?
  • A. weak is non-optional, unowned is optional
  • B. weak can become nil; unowned assumes the referent always outlives it and is non-optional ✓
  • C. They are identical
  • D. unowned increases retain count
Correct answer: B. weak references become nil when deallocated, while unowned assume the object outlives the reference and crash if not.
In SwiftUI, when should you use @StateObject versus @ObservedObject?
  • A. @StateObject for a view that creates and owns the object; @ObservedObject when it is passed in ✓
  • B. @ObservedObject creates the object; @StateObject observes it
  • C. They are interchangeable in all cases
  • D. @StateObject is only for structs
Correct answer: A. @StateObject is used where the view instantiates and owns the object's lifecycle; @ObservedObject observes one passed from elsewhere.
Why must UI updates in iOS be performed on the main thread?
  • A. Background threads cannot allocate memory
  • B. UIKit and SwiftUI are not thread-safe and expect UI work on the main queue ✓
  • C. The main thread has more memory
  • D. It improves battery life only
Correct answer: B. UIKit/SwiftUI are not thread-safe, so touching UI off the main thread causes undefined behavior and crashes.
In Grand Central Dispatch, what does 'DispatchQueue.global(qos: .userInitiated).async' do?
  • A. Runs work synchronously on the main queue
  • B. Schedules work on a background concurrent queue with a given priority ✓
  • C. Blocks the main thread until finished
  • D. Creates a new serial queue on the main thread
Correct answer: B. It asynchronously dispatches work to a global concurrent background queue at the specified quality-of-service level.
What is the main advantage of a struct over a class in Swift for a simple model?
  • A. Structs support inheritance
  • B. Structs are value types, giving copy semantics and avoiding shared mutable state ✓
  • C. Structs are always faster to compile
  • D. Structs can be deinitialized with deinit
Correct answer: B. Structs are value types copied on assignment, which avoids unintended shared mutation common with reference-type classes.
In the Codable protocol, what does 'CodingKeys' allow you to customize?
  • A. The encoding algorithm
  • B. The mapping between Swift property names and JSON keys ✓
  • C. The network timeout
  • D. The date format automatically
Correct answer: B. CodingKeys lets you map differing JSON key names to your Swift property names during encode/decode.
What does 'guard let' provide over 'if let' in Swift?
  • A. It never unwraps optionals
  • B. It unwraps and, on failure, requires an early exit, keeping the unwrapped value in scope afterward ✓
  • C. It runs asynchronously
  • D. It creates a weak reference
Correct answer: B. guard let unwraps for the remainder of the scope and mandates an early return on the else path, reducing nesting.
When a UITableView reuses cells via dequeueReusableCell, what common bug must you guard against?
  • A. Cells never appear
  • B. Stale content or misapplied async image loads from a previously used cell ✓
  • C. Constraints being ignored
  • D. The table scrolling backward
Correct answer: B. Reused cells retain prior state, so you must reset content and cancel/guard async updates to avoid showing stale data.
What does the @escaping attribute on a closure parameter signify?
  • A. The closure runs immediately and cannot be stored
  • B. The closure may be called after the function returns, so it can outlive the call ✓
  • C. The closure cannot capture self
  • D. The closure is executed on the main thread
Correct answer: B. @escaping marks that the closure may be stored and invoked after the enclosing function has returned.
You capture `self` strongly inside an escaping closure held by an object, creating a retain cycle. What is the standard fix?
  • A. Use [strong self]
  • B. Use [weak self] in the capture list ✓
  • C. Mark the closure @escaping
  • D. Make self a struct
Correct answer: B. Capturing `[weak self]` breaks the strong reference cycle by holding a weak, optional reference.
In Swift concurrency, what does marking a class with `@MainActor` guarantee?
  • A. It runs on a background thread
  • B. Its members are accessed on the main thread ✓
  • C. It becomes thread-unsafe
  • D. It disables async/await
Correct answer: B. @MainActor isolates access to the main actor, ensuring UI-related code runs on the main thread.
A UITableView with 10,000 rows must scroll smoothly. Which mechanism makes this efficient?
  • A. Loading all cells at launch
  • B. Cell reuse via dequeueReusableCell ✓
  • C. Disabling Auto Layout
  • D. Rendering on a background thread
Correct answer: B. Dequeuing reusable cells recycles a small pool of views instead of allocating one per row.
In SwiftUI, when should you use @StateObject instead of @ObservedObject for a reference-type view model?
  • A. Never, they are identical
  • B. When the view creates and owns the object's lifecycle ✓
  • C. Only for value types
  • D. Only in previews
Correct answer: B. @StateObject ensures the view owns and persists the object across re-renders, avoiding accidental re-creation.
Your app makes a network call and updates a UILabel, but the UI freezes or crashes. What is the likely cause?
  • A. Updating UI off the main thread ✓
  • B. Using URLSession instead of a socket
  • C. Missing an Info.plist key
  • D. Too many constraints
Correct answer: A. UIKit is not thread-safe; UI updates must be dispatched back to the main thread.
In Core Data, what is the purpose of an NSManagedObjectContext?
  • A. It stores the SQLite file
  • B. It is an in-memory scratchpad tracking object changes before saving ✓
  • C. It defines the data model schema
  • D. It handles push notifications
Correct answer: B. The context tracks inserted/updated/deleted managed objects in memory until save() persists them.
Which approach best keeps a heavy image-decoding task from blocking the main thread?
  • A. Run it synchronously in viewDidLoad
  • B. Perform decoding on a background queue, then update UI on main ✓
  • C. Use a larger UIImageView
  • D. Increase the app's memory limit
Correct answer: B. Offloading decoding to a background queue keeps the main thread free, then results are applied on main.
In Swift, what does the `Codable` protocol enable?
  • A. Automatic UI generation
  • B. Encoding and decoding to/from formats like JSON ✓
  • C. Thread synchronization
  • D. Dependency injection
Correct answer: B. Codable combines Encodable and Decodable for serializing types to and from JSON and other formats.
Why might `weak var delegate` be preferred for a delegate property in a protocol-delegate pattern?
  • A. It improves rendering speed
  • B. It avoids a strong reference cycle between object and delegate ✓
  • C. It makes the delegate optional at compile time
  • D. It enforces thread safety
Correct answer: B. A weak delegate reference prevents a retain cycle since the owner usually also holds the delegate.
In SwiftUI, what does the `.task` modifier do compared to `.onAppear`?
  • A. It runs synchronous code only
  • B. It runs an async task tied to the view's lifetime, auto-cancelled on disappear ✓
  • C. It replaces the body
  • D. It schedules a timer
Correct answer: B. .task launches async work bound to the view lifecycle and cancels it automatically when the view disappears.

Hard round 30 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.
In Swift, why can capturing 'self' strongly in a closure stored by an object cause a retain cycle, and how is it resolved?
  • A. Closures cannot capture self; use a global
  • B. The object retains the closure and the closure retains self; resolve with [weak self] ✓
  • C. self is always weak; no fix needed
  • D. Use a struct instead of any closure
Correct answer: B. A stored closure capturing self strongly creates a mutual retain; a [weak self] capture list breaks the cycle.
What is the key behavior of copy-on-write for a Swift array?
  • A. Arrays are reference types with no copying
  • B. Arrays behave as values but defer the actual copy until a mutation occurs on a shared buffer ✓
  • C. Arrays always copy immediately on assignment
  • D. Copy-on-write only applies to classes
Correct answer: B. Swift arrays are value types but use copy-on-write, physically copying the backing buffer only when a shared instance is mutated.
When using async/await with an actor, what does actor isolation guarantee?
  • A. Code runs on the main thread only
  • B. Mutable state is accessed serially, preventing data races on that actor's state ✓
  • C. All methods become synchronous
  • D. The actor cannot be captured in closures
Correct answer: B. Actors serialize access to their mutable state, preventing concurrent data races without manual locks.
Why can calling DispatchQueue.main.sync from the main thread cause a deadlock?
  • A. The main queue is concurrent
  • B. The current work waits for the queued block, but the serial main queue can't run it until the current work finishes ✓
  • C. sync always deadlocks on any queue
  • D. The block runs on a background thread
Correct answer: B. The main queue is serial; calling sync from it blocks waiting for a block that can't start until the caller returns, deadlocking.
In UIKit, what is the difference between a view's 'frame' and 'bounds' after a rotation transform is applied?
  • A. They are always identical
  • B. bounds stays in the view's own coordinate space; frame becomes the smallest axis-aligned rectangle enclosing the transformed view ✓
  • C. frame is unaffected by transforms
  • D. bounds includes the superview origin
Correct answer: B. bounds is in the view's local coordinate system, while frame after a transform is the enclosing axis-aligned rect in the superview.
What problem does the @MainActor attribute solve in Swift concurrency?
  • A. It parallelizes UI code
  • B. It guarantees the annotated code runs on the main actor/thread, safely serializing UI updates ✓
  • C. It disables the main thread
  • D. It converts async code to sync
Correct answer: B. @MainActor ensures the code hops to and runs on the main actor, making UI updates thread-safe by construction.
In Core Data with multiple contexts, what is the recommended pattern for background work?
  • A. Share one context across all threads
  • B. Use a private-queue context and perform work inside performBackgroundTask / perform blocks, then merge ✓
  • C. Access managed objects directly from any thread
  • D. Disable the persistent store coordinator
Correct answer: B. Managed objects/contexts are not thread-safe; use per-queue contexts and their perform blocks, merging changes to the main context.
Why does an implicitly unwrapped optional (Type!) trade safety for convenience?
  • A. It cannot be nil ever
  • B. It behaves like a normal optional but crashes on access if still nil, so it assumes the value is set before first use ✓
  • C. It makes the property lazy
  • D. It converts to a computed property
Correct answer: B. An implicitly unwrapped optional auto-unwraps on access and crashes if nil, trading compile-time safety for terser syntax.
When bridging Objective-C to Swift, why might a method used as a #selector target need the @objc attribute?
  • A. Swift methods are all Objective-C compatible by default
  • B. The Objective-C runtime needs the method exposed via @objc for selector-based dynamic dispatch ✓
  • C. @objc makes the method faster
  • D. Selectors only work with structs
Correct answer: B. Selector-based APIs use the Objective-C runtime, which requires the method to be exposed with @objc to be found dynamically.
Why can a Task { } created in a SwiftUI button action inadvertently outlive the view, and how do you tie it to the view lifecycle?
  • A. Tasks are always cancelled with the view
  • B. An unstructured Task is independent; use the .task modifier or store/cancel it so it cancels when the view disappears ✓
  • C. Tasks cannot be created in views
  • D. Use DispatchQueue instead
Correct answer: B. An unstructured Task runs independently of the view; the .task modifier binds a task to the view's lifecycle and cancels it on disappear.
In Swift's ARC, an object A holds a strong ref to B, and B needs to reference A without keeping it alive. Which is the safest choice when B's reference may become nil?
  • A. unowned reference
  • B. weak reference ✓
  • C. strong reference
  • D. implicitly unwrapped strong
Correct answer: B. A weak reference becomes nil safely when the target deallocates, whereas unowned crashes on access after dealloc.
You see a data race on a shared mutable dictionary accessed from multiple queues. Using a serial dispatch queue with a concurrent read barrier, which pattern is correct?
  • A. Sync reads, sync writes
  • B. Concurrent queue: sync reads, async barrier writes ✓
  • C. All async no barrier
  • D. Global queue with no sync
Correct answer: B. The reader-writer pattern uses concurrent sync reads and async barrier writes for safe, performant access.
In SwiftUI, a List scrolls poorly because each row recomputes expensive derived data on every diff. What is the best remedy?
  • A. Add more @State to the row
  • B. Precompute/memoize data in the model and give rows stable Identifiable IDs ✓
  • C. Wrap each row in a GeometryReader
  • D. Force full-view redraws
Correct answer: B. Stable identity plus precomputed model data lets SwiftUI diff minimally and skip redundant recomputation.
An app is terminated by the OS with a memory jetsam event. Which tool/technique best identifies the abstract retained-memory growth?
  • A. Time Profiler for CPU
  • B. Instruments Allocations/Leaks and the memory graph debugger ✓
  • C. Network Link Conditioner
  • D. Console print statements only
Correct answer: B. Allocations/Leaks instruments and the memory graph debugger reveal retained objects and cycles causing growth.
Using Swift async/await, you launch several independent network fetches that should run in parallel and all complete. Which construct is idiomatic?
  • A. A sequential for-await loop
  • B. async let bindings or a TaskGroup ✓
  • C. DispatchGroup with completion handlers
  • D. Nested completion closures
Correct answer: B. `async let` or a TaskGroup starts child tasks concurrently and awaits their combined results structurally.
In Core Data with a background context saving while the main context reads, you must merge changes safely. What is the correct setup?
  • A. Share one context across threads
  • B. Use parent/child contexts or automaticallyMergesChangesFromParent with proper context confinement ✓
  • C. Disable the persistent store
  • D. Save only on the main thread
Correct answer: B. Contexts are thread-confined; a parent/child or merge-notification setup safely propagates saved changes.
A value-type struct with a large array is copied frequently, hurting performance. Which Swift feature mitigates this cost automatically?
  • A. Manual reference counting
  • B. Copy-on-write (COW) used by standard library collections ✓
  • C. Marking the struct final
  • D. Boxing into NSObject
Correct answer: B. Standard collections use copy-on-write, deferring the actual copy until a mutation occurs on a shared buffer.
You must ensure a Swift actor's method that awaits inside does not assume state is unchanged after the suspension point. This hazard is called:
  • A. Priority inversion
  • B. Actor reentrancy ✓
  • C. Deadlock
  • D. Retain cycle
Correct answer: B. Actors are reentrant, so other calls can mutate state during an await; code must re-validate assumptions afterward.
For a smooth 120Hz ProMotion animation, which is the most important consideration versus a 60Hz baseline?
  • A. Halving image resolution
  • B. Keeping per-frame work well under the shorter frame budget (~8.3ms) ✓
  • C. Disabling Core Animation
  • D. Rendering on the main thread only
Correct answer: B. At 120Hz each frame budget shrinks to ~8.3ms, so per-frame work must fit that tighter window to avoid drops.
You need to persist a small amount of sensitive data like an auth token securely on device. Which storage is appropriate?
  • A. UserDefaults
  • B. Keychain Services ✓
  • C. A plist in Documents
  • D. In-memory only
Correct answer: B. The Keychain provides encrypted, access-controlled storage suited for secrets, unlike plain UserDefaults.

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: August 2026.