iOS Developer interview questions
100 real questions with model answers and explanations for Middle candidates.
See a iOS Developer resume example →Practice with flashcards
Spaced repetition · Hunter Pass
Questions
SwiftUI Views are transient value descriptions, while durable state is stored through state-management mechanisms outside an individual View value.
- SwiftUI can create new View values whenever inputs change, so a View struct should not be treated as a persistent UI object.
- Property wrappers connect the description to storage owned by the framework, another object, or the environment.
- State changes invalidate dependent parts of the hierarchy and cause SwiftUI to recompute their body values.
Why interviewers ask this: The interviewer checks whether you understand declarative rendering rather than treating a SwiftUI View like a UIKit view controller.
@State gives a View identity local source-of-truth storage for a small mutable value.
- SwiftUI owns the storage and preserves it across new instances of the same View identity.
- Mutating the wrapped value invalidates body computations that read it.
- The property should be private when it is an implementation detail, and its initial value should not be used as an external synchronization channel.
Why interviewers ask this: A strong answer explains framework-owned storage, View identity, and why @State is not general shared state.
@Binding provides read-write access to state owned elsewhere without taking ownership of that storage.
- A parent commonly passes $property to a child that needs to edit the value.
- Reading the binding returns the current source value, and writing it updates that same source.
- A Binding can also be constructed with custom get and set closures to adapt an existing state model.
Why interviewers ask this: The interviewer checks whether you can distinguish borrowed mutation access from ownership of state.
ObservableObject exposes change notifications, and @Published emits those notifications when a marked property is about to change.
- ObservableObject provides an objectWillChange publisher used by SwiftUI observation.
- @Published creates a publisher for the individual property as well as participating in the enclosing object's change signal.
- Changes must occur on the appropriate actor for UI use, commonly by isolating the observable model to @MainActor.
Why interviewers ask this: A good answer connects object-level invalidation, property publishers, and UI isolation.
@ObservedObject is appropriate when a View observes an ObservableObject whose lifetime is owned outside that View.
- The wrapper subscribes the View to object change notifications.
- Recreating the View does not establish stable ownership of a newly initialized object.
- Parent injection or another external owner should keep the observed instance alive for the required lifetime.
Why interviewers ask this: The interviewer is evaluating whether you separate observation from object creation and ownership.
@StateObject is appropriate when a View identity creates and owns the lifetime of an ObservableObject.
- SwiftUI initializes the object once for that identity rather than on every View value reconstruction.
- Object change notifications still invalidate dependent UI like they do with @ObservedObject.
- The storage ends when the owning View identity leaves the hierarchy.
Why interviewers ask this: A strong answer ties @StateObject to stable reference ownership and View identity.
Replacing @StateObject with @ObservedObject removes the View's framework-managed ownership of the object.
- An object initialized inline with @ObservedObject may be recreated as new View values are produced.
- Reinitialization can reset state and repeat subscriptions or other initialization work.
- If an external owner already supplies the instance, @ObservedObject is correct because adding a second owner would misrepresent the data flow.
Why interviewers ask this: The interviewer checks whether you can reason about wrapper choice from lifetime rather than syntax.
@EnvironmentObject retrieves a shared ObservableObject supplied by an ancestor in the SwiftUI environment.
- It avoids passing the same object explicitly through every intermediate View.
- Resolution is type-based, so the hierarchy must contain a matching environmentObject injection.
- It suits genuinely shared dependencies, but excessive use can hide a View's requirements and complicate previews or tests.
Why interviewers ask this: A good answer covers type-based lookup and the trade-off between convenient propagation and explicit dependencies.
@Environment reads a value identified by an environment key, while @EnvironmentObject resolves an observable reference by type.
- Environment values include system inputs such as locale, color scheme, and dismiss actions.
- Custom EnvironmentKey definitions can propagate lightweight values or dependencies.
- @EnvironmentObject specifically observes an injected ObservableObject and requires that object to exist in the ancestor hierarchy.
Why interviewers ask this: The interviewer checks whether you distinguish key-based environment values from type-based observable objects.
SwiftUI associates state storage with a View's structural or explicit identity rather than with a particular struct instance.
- Keeping the same identity lets @State and @StateObject storage survive body recomputation.
- Changing identity with a different branch, position, or id can discard old storage and create new storage.
- Stable identity is therefore part of the correctness of lists, conditional hierarchies, and stateful child Views.
Why interviewers ask this: A strong answer explains state resets through identity rules rather than blaming arbitrary redraws.
A Publisher produces a typed sequence, a Subscriber receives it, and a Subscription connects them and manages demand and cancellation.
- A Publisher declares both Output and Failure types.
- A Subscriber receives a subscription, zero or more values, and at most one completion.
- The Subscription lets the subscriber request demand and cancel further delivery.
Why interviewers ask this: The interviewer checks whether you know Combine's core protocol roles rather than only convenience operators.
Every Combine Publisher specifies the value type it emits and the error type with which it can fail.
- A publisher that cannot fail uses Never as its Failure type.
- Operators must preserve or deliberately transform compatible Output and Failure types.
- mapError, setFailureType, catch, and replaceError adapt the failure channel for downstream composition.
Why interviewers ask this: A good answer demonstrates type-level reasoning about values and terminal errors in a pipeline.
An AnyCancellable represents cancellation ownership, and releasing it cancels the associated subscription.
- sink and assign commonly return an AnyCancellable for the created subscriber.
- Storing it in a property or Set keeps the subscription active for that owner's lifetime.
- Cancelling explicitly or releasing the storage stops future delivery and breaks the subscription chain.
Why interviewers ask this: The interviewer is evaluating whether you understand subscription lifetime as ordinary ownership.
PassthroughSubject forwards new values only, while CurrentValueSubject also stores and exposes a current value.
- A new PassthroughSubject subscriber receives nothing until the next send.
- A new CurrentValueSubject subscriber immediately receives its current value before later updates.
- Both can imperatively send values and a terminal completion into an otherwise declarative pipeline.
Why interviewers ask this: A strong answer distinguishes event streams from state-like streams.
map transforms each emitted value directly, while flatMap transforms each value into a new Publisher and merges those inner publishers.
- map keeps a one-value-to-one-value relationship and does not introduce another asynchronous stream.
- flatMap is useful when an upstream value starts work represented by a Publisher.
- flatMap does not automatically cancel older inner publishers, so switchToLatest is a separate choice when only the newest stream should remain active.
Why interviewers ask this: The interviewer checks whether you understand publisher flattening and do not confuse flatMap with simple value transformation.
combineLatest emits from the latest values after each input has emitted once, while zip pairs values by their emission positions.
- combineLatest reacts whenever either upstream updates and reuses the latest value from the other.
- zip waits until it has one unmatched value from each side and consumes them as a pair.
- The choice depends on whether the relationship represents current state or one-to-one event pairing.
Why interviewers ask this: A good answer explains emission semantics rather than merely naming two combining operators.
subscribe(on:) affects where subscription and upstream work begin, while receive(on:) changes the scheduler used for downstream delivery.
- subscribe(on:) influences subscription, request, and cancellation operations upstream.
- receive(on:) delivers subsequent values and completion on the specified scheduler.
- UI-consuming stages commonly receive on the main scheduler, but unnecessary scheduler hops should not be scattered through a pipeline.
Why interviewers ask this: The interviewer checks whether you can place scheduling boundaries according to upstream work and downstream consumers.
eraseToAnyPublisher hides a concrete publisher chain behind the stable AnyPublisher type.
- Operator chains have long implementation-specific generic types that callers rarely need to know.
- Type erasure prevents implementation changes from leaking into an API signature.
- It preserves Output and Failure types while removing access to concrete publisher-specific operations.
Why interviewers ask this: A strong answer connects type erasure to API boundaries rather than presenting it as required for every pipeline.
async marks a function that can suspend, and await marks a call where the current task may suspend until an asynchronous result is available.
- Suspension does not block the underlying thread while the task waits.
- An async function can return a value or throw, producing async throws when both behaviors apply.
- await marks a potential suspension point but does not guarantee that suspension or a thread switch occurs.
Why interviewers ask this: The interviewer checks whether you distinguish task suspension from thread blocking and scheduling.
async let creates a scoped child task for a value that can be computed concurrently with sibling work.
- Several async let bindings begin their child operations before their values are awaited.
- Reading a binding requires await and propagates errors when the child expression throws.
- Unfinished child tasks are awaited or cancelled automatically before the enclosing scope exits.
Why interviewers ask this: A good answer identifies async let as structured concurrency for a fixed number of child results.
Locked questions
- 21
When is withTaskGroup different from async let?
asyncconcurrency - 22
What context does Task { } inherit in Swift concurrency?
concurrencyswiftownership - 23
How does Task.detached differ from Task { }?
- 24
How does cancellation work in Swift concurrency?
concurrencyswiftresilience - 25
What isolation guarantee does a Swift actor provide?
swiftconcurrency - 26
What does @MainActor guarantee?
- 27
What is actor reentrancy in Swift?
swiftconcurrency - 28
What does Sendable mean in Swift concurrency?
concurrencyswift - 29
How does Automatic Reference Counting manage memory in Swift?
memoryswift - 30
How do value types and reference types differ in ownership behavior?
value-semanticsownership - 31
How does a strong reference cycle arise?
memory - 32
What semantics does a weak reference have in Swift?
swift - 33
When is unowned different from weak in Swift?
memoryswift - 34
How can an escaping closure create a retain cycle with its owner?
closuresmemory - 35
Why are delegate properties commonly declared weak?
delegationprotocols - 36
What do Encodable, Decodable, and Codable represent?
codable - 37
When can the compiler synthesize Codable conformance?
codable - 38
How do keyed, unkeyed, and single-value Codable containers differ?
containerscodable - 39
How do serial and concurrent DispatchQueues differ?
concurrency - 40
What is the difference between sync and async dispatch in GCD?
asyncconcurrency - 41
What do DispatchGroup and queue quality of service provide?
data-structures - 42
What does protocol-oriented design mean in Swift?
typingswiftprotocols - 43
How does dispatch differ for protocol requirements and extension-only methods?
typingprotocols - 44
What is an associated type in a Swift protocol?
typingswiftprotocols - 45
How do generic constraints and where clauses improve a Swift generic API?
genericsswiftapi - 46
What is the difference between some Protocol and any Protocol in Swift?
typingswiftprotocols - 47
What problem does type erasure solve in Swift?
swift - 48
What responsibilities belong to Model, View, and ViewModel in MVVM?
architecture-patterns - 49
How can a SwiftUI View observe an MVVM ViewModel?
swiftuiswiftarchitecture-patterns - 50
What should an MVVM ViewModel avoid owning?
architecture-patterns - 51
How would you implement a product list screen that handles loading, content, empty, and error states?
- 52
How would you add debounced search to a SwiftUI screen without displaying stale results?
debounceswiftuiswift - 53
How would you implement reliable infinite scrolling for a feed?
- 54
How would you build an offline-capable list backed by Core Data?
core-data - 55
How would you resolve conflicting offline edits when the same record changed on the server?
- 56
How would you implement image loading for a fast-scrolling feed?
- 57
How would you implement a multi-step form in SwiftUI with validation and submission?
formsswiftuivalidation - 58
How would you implement deep links into nested authenticated screens?
- 59
How would you route a push notification to the correct feature when the app is in different lifecycle states?
- 60
How would you add optimistic updates to a SwiftUI favorite button?
swiftuilockingswift - 61
A SwiftUI child screen keeps losing its ViewModel state during parent updates; how would you debug and fix it?
swiftuiswift - 62
How would you share editable state across several SwiftUI screens without creating inconsistent copies?
swiftuiswift - 63
Where would you start asynchronous side effects in a SwiftUI screen?
asyncswiftconcurrency - 64
How would you keep a SwiftUI ViewModel thread-safe while loading and transforming data?
concurrencyswiftswiftui - 65
How would you restore a SwiftUI NavigationStack after app relaunch?
swiftuinavigationswift - 66
How would you embed an existing UIKit controller in SwiftUI and keep updates predictable?
uikitswiftuilearning - 67
How would you expose a SwiftUI feature inside an existing UIKit navigation flow?
uikitswiftuinavigation - 68
How would you verify accessibility while implementing a custom SwiftUI control?
a11yswiftuiswift - 69
A dismissed screen never deinitializes; how would you find the retain cycle?
memory - 70
How would you debug a retain cycle involving a Combine sink in a ViewModel?
combinememory - 71
A production crash appears only in unsymbolicated reports; what steps would you take?
- 72
How would you investigate an EXC_BAD_ACCESS crash in Swift code?
swift - 73
How would you diagnose UIKit updates happening off the main thread?
uikitconcurrency - 74
How would you investigate a data race in a shared in-memory cache?
cachingmemory - 75
How would you debug an intermittent index out of range crash in a collection view?
viewsindexes - 76
Users report that the app freezes for several seconds without crashing; how would you investigate?
scheduling - 77
How would you debug a deadlock between legacy GCD code and @MainActor code?
lockingconcurrency - 78
How would you profile and reduce high CPU usage in an iOS feature?
- 79
A UIKit list scrolls poorly when images and text appear; how would you optimize it?
uikitoptimization - 80
How would you reduce memory spikes caused by loading many photos?
memory - 81
A SwiftUI screen recomputes and redraws too much; how would you find and reduce invalidations?
swiftuiswift - 82
How would you improve a slow cold launch?
- 83
How would you design a testable URLSession networking layer with typed endpoints?
designendpointsnetworking - 84
How would you prevent multiple failed requests from refreshing an expired access token at the same time?
tokens - 85
How would you decide which network requests may be retried automatically?
- 86
How would you propagate cancellation from a disappearing SwiftUI screen to URLSession?
swiftuiresilienceswift - 87
How would you map networking failures into useful user-facing errors?
- 88
How would you add HTTP caching to a URLSession client?
cachinghttpnetworking - 89
How would you inject different networking implementations into iOS features without a global singleton?
- 90
How would you debug an API request that works in a browser but fails in the iOS app?
api - 91
How would you unit test an @MainActor MVVM ViewModel?
architecture-patternsunit - 92
How would you test cancellation and out-of-order completion in async Swift code?
asyncswiftconcurrency - 93
When would you use URLProtocol instead of a mocked networking protocol in tests?
typingprotocolstesting - 94
How would you test a Core Data repository without polluting persistent user data?
core-data - 95
How would you make an XCTest UI test for login less flaky?
flaky - 96
How would you choose tests for a new offline search feature?
testing - 97
A test passes locally but fails intermittently in CI; how would you investigate it?
- 98
How would you add a regression test for a production crash caused by a malformed API payload?
regressionapi - 99
How would you reduce a slow iOS test suite without losing useful coverage?
coverage - 100
How would you design and validate an offline-capable paginated SwiftUI feed with images and deep links?
swiftuidesignvalidation