iOS Developer interview questions
100 real questions with model answers and explanations for Senior candidates.
See a iOS Developer resume example →Practice with flashcards
Spaced repetition · Hunter Pass
Questions
I would keep the UIKit coordinator as the flow owner and embed the SwiftUI checkout behind one UIHostingController factory.
- The factory accepts a typed checkout input and returns completion actions such as paid, cancelled, or authenticationRequired, without exposing SwiftUI state to UIKit.
- One @Observable or TCA store owns the checkout state; the hosting controller only bridges navigation bar, dismissal, and environment dependencies.
- I would test push, modal dismissal, deep link entry, and memory release across 20 repeated presentations before adding a second SwiftUI flow.
Why interviewers ask this: The interviewer is checking whether SwiftUI adoption preserves one navigation and state owner at the framework boundary.
I would use UIViewControllerRepresentable with a Coordinator that owns the delegate bridge and keeps capture lifecycle out of updateUIViewController.
- makeUIViewController creates the controller once, while updateUIViewController applies only changed configuration such as zoom or selected camera.
- The Coordinator converts delegate callbacks into typed actions and does not retain the parent wrapper or controller in a cycle.
- dismantleUIViewController stops the session, clears the delegate, and is verified by presenting and dismissing the screen 50 times with a stable object count.
Why interviewers ask this: A strong answer respects representable identity, delegate ownership, and teardown instead of recreating an expensive UIKit controller on every update.
I would choose TCA for this checkout because explicit state transitions and effect cancellation outweigh its ceremony at this complexity.
- Address, quote, payment, and submission remain one feature state with scoped child reducers instead of four view models mutating shared services.
- Each request receives a cancellation ID, and changing the address cancels the stale quote before a new effect starts.
- I would prove the choice with reducer tests for 9 actions, stale responses, and double-submit, while a simple receipt screen stays plain SwiftUI or MVVM.
Why interviewers ask this: The interviewer is evaluating a pattern choice against concrete state and side-effect complexity rather than preference.
I would debounce the query and make the search effect cancellable by one stable cancellation ID.
- A queryChanged action updates local state immediately, then starts a 300 ms clock-driven debounce before requesting results.
- cancelInFlight ensures a new query cancels the previous effect, and the response action carries the query or request ID for a final stale-result check.
- A TestStore with a TestClock advances 299 and 1 milliseconds and proves that typing five characters produces one request and ignores the cancelled response.
Why interviewers ask this: A strong answer makes timing, cancellation, and stale-response protection deterministic in both implementation and tests.
I would store typed route state outside the views and derive NavigationStack paths from that single source of truth.
- The route enum carries only stable identifiers, while validation maps the deep link to steps 1 through 3 and rejects missing prerequisites.
- Scene storage persists a versioned codable route snapshot, but sensitive form values stay in the feature store or Keychain as appropriate.
- Tests cover cold launch, malformed links, back navigation, and a schema version change so restoration never builds an impossible path.
Why interviewers ask this: The interviewer is checking state-driven navigation, validated deep links, and versioned restoration rather than imperative view pushes.
I would break cycles at owned contracts and optimize the measured high-churn dependency paths, not merge all 42 targets blindly.
- Xcode build timing and the package graph identify shared targets that invalidate 30 or more dependents after small edits.
- Cross-feature route and result protocols move into narrow interface targets, while the app composition target supplies implementations and orchestration.
- CI rejects cycles and unauthorized imports, and I keep the change only if representative incremental builds fall below 90 seconds without excessive package overhead.
Why interviewers ask this: A strong answer treats modularization as dependency-graph design with measurable build outcomes.
I would publish consumer-shaped capabilities instead of making the concrete 18-method service a cross-package API.
- Checkout receives CurrentAccountProviding and AddressUpdating, while profile receives only the broader mutations it actually owns.
- Domain values crossing the boundary are Sendable structs, and transport DTOs, URLSession, and persistence models remain internal.
- Contract tests run against the live implementation and fakes, while the composition root adapts one AccountService to each narrow protocol.
Why interviewers ask this: The interviewer is evaluating interface segregation, package visibility, and concurrency-safe boundary models.
I would split stable design primitives from volatile product components and remove umbrella imports from feature code.
- Color, typography, spacing, and five mature controls form a low-churn DesignCore target with a deliberately small public API.
- Product-specific banners and experiments stay with their owning features instead of entering SharedUI after a second use.
- I would compare 20 incremental builds before and after, requiring lower invalidation time without increasing launch time or duplicate resources.
Why interviewers ask this: A strong answer uses change frequency and ownership to reduce build invalidation instead of creating another generic shared package.
I would define one typed feature dependency value and supply it at the package entry point from the app composition root.
- Required services have no silent production singleton fallback, so a missing dependency fails during construction rather than during checkout.
- Clock and UUID dependencies use controllable implementations, while networking and analytics expose only the operations the feature calls.
- A test builds the feature with four fakes in under 10 milliseconds, and package code contains zero global container lookups.
Why interviewers ask this: The interviewer is checking explicit dependency ownership and deterministic seams without a hidden service locator.
I would declare the resources on the owning target and resolve them through Bundle.module inside that package.
- Process localized catalogs and assets, but copy the JSON only when its directory structure or raw bytes must remain unchanged.
- The public feature API returns semantic image and string values rather than exposing bundle paths to consumers.
- Package tests load one localized string in two locales, decode the fixture, and verify the release archive contains one copy of each resource.
Why interviewers ask this: A strong answer knows SPM resource ownership and avoids coupling consumers to package bundle layout.
I would expose a feature factory and typed inputs and outputs, with separate hosting adapters over one state and domain implementation.
- The SwiftUI adapter returns the feature view, while the UIKit adapter wraps the same view in UIHostingController and owns containment details.
- Availability-specific behavior stays in adapters, so the domain target does not import UIKit or branch on iOS versions.
- Integration tests run the same three user journeys through both hosts and assert identical actions, navigation results, and deallocation.
Why interviewers ask this: The interviewer is checking whether reusable feature logic stays independent of its UIKit and SwiftUI hosts.
I would profile cell updates and identity first because hosting SwiftUI is not by itself proof of the bottleneck.
- Time Profiler and the Core Animation instrument separate body work, Auto Layout, image decoding, and diffable snapshot application during the same scroll trace.
- Stable item IDs and presentation-ready row models prevent full subtree replacement, while image work stays outside cell configuration.
- I would keep the boundary if p95 hitch duration stays below one 16.67 ms frame on target hardware after 10 identical scroll runs.
Why interviewers ask this: A strong answer measures the UIKit and SwiftUI seam and corrects identity or work placement before rewriting it.
I would remove the strong cycle at the closure edge and make the completion lifetime explicit.
- The controller owns the closure, so a strong self capture creates controller to closure to controller; the memory graph should show that exact path.
- If completion is optional after dismissal, capture self weakly and return when it is gone; if completion must finish, move the work to a longer-lived owner.
- Clear one-shot callbacks after invocation and add a test that presents and dismisses the controller 30 times with zero surviving instances.
Why interviewers ask this: The interviewer is checking ARC graph reasoning and whether weak capture matches the required lifetime semantics.
I would use weak unless the design can prove the parent outlives every access to the child reference.
- weak becomes nil safely when the parent is released and fits asynchronous completion or teardown that may arrive late.
- unowned avoids optional handling but traps on access after deallocation, so it is suitable only for a strict shared-lifetime invariant.
- A lifecycle test releases the parent before a delayed child callback; if that sequence is legal, unowned is invalid by construction.
Why interviewers ask this: A strong answer chooses weak or unowned from a proven lifetime invariant rather than style.
The task strongly retains the view model for the duration of load, so I would give the task an owner and cancel it when the feature ends.
- Store the Task handle, cancel it on replacement or teardown, and ensure load propagates cancellation into URLSession instead of swallowing CancellationError.
- A weak capture alone is insufficient if guard let self promotes a strong reference before a five-second await; narrow the strong lifetime or move durable work to a service.
- A test dismisses after 100 milliseconds, verifies cancellation reaches the request, and expects the view model to deallocate within one run-loop turn.
Why interviewers ask this: The interviewer is testing closure capture across suspension and explicit ownership of unstructured tasks.
I would inspect each registration by who retains whom and where its useful lifetime ends.
- A block timer retains its closure, a stored AnyCancellable retains the subscription chain, and either can close a cycle through self.
- Delegate properties are weak when the delegate does not own the source, while selector-based NotificationCenter observers still need correct semantic teardown even if automatic removal prevents a crash.
- Teardown invalidates the timer, cancels subscriptions, clears callbacks, and a 50-cycle memory test confirms the screen count returns to baseline.
Why interviewers ask this: A strong answer reasons about four different lifetime mechanisms instead of applying weak self mechanically.
I would bound temporary Objective-C objects with nested autorelease pools and process the import in measured batches.
- Instruments Allocations should show whether bridged NSString, NSDictionary, or formatter objects accumulate until the outer run loop drains.
- Process 500 or 1,000 records inside autoreleasepool blocks, persist the result, then release the batch before reading the next one.
- I would tune batch size until peak memory stays below 300 MB without making the 80,000-record import more than 10% slower.
Why interviewers ask this: The interviewer is checking ARC interoperability with autoreleased Objective-C objects and quantitative batching.
No, value semantics guarantees independent observable values, not constant-time copying for every custom type.
- Data and standard collections often use copy-on-write, so unchanged copies can share storage until a mutation requires uniqueness.
- A custom struct containing a large reference-backed buffer needs an explicit copy-on-write box and isKnownUniquelyReferenced before mutation.
- I would measure allocations for 1,000 passes and keep the value design only if mutation copies occur at the intended ownership boundary.
Why interviewers ask this: A strong answer separates value semantics from implementation cost and knows when custom copy-on-write is justified.
I would keep UI state isolated to MainActor but move decoding into a non-main, Sendable service boundary.
- URLSession awaits without blocking the actor, but synchronous JSONDecoder work executed after resumption can still occupy MainActor.
- A decoder service returns a Sendable domain snapshot, then the view model performs only the final state assignment on MainActor.
- Main Thread Checker and signposts must show no main-thread block above 16 ms and the same decoded result across 100 fixture runs.
Why interviewers ask this: The interviewer is checking that actor isolation is not confused with where CPU-heavy synchronous work should execute.
I would treat the await as a reentrancy point and reserve or version the stock before leaving actor isolation.
- One option decrements to a reserved state atomically, then restores the unit if authorization fails or is cancelled.
- Another captures a version and revalidates it after await, but then only one buyer may commit and the other needs a defined payment reversal.
- A deterministic test suspends both authorizations at the same point and proves exactly one order reaches committed state.
Why interviewers ask this: A strong answer understands actor reentrancy and preserves a business invariant across suspension.
Locked questions
- 21
A gallery must download and downsample 200 images, but launching all child tasks at once causes a memory spike. How do you use structured concurrency?
concurrencyswiftmemory - 22
A SwiftUI search screen starts a new async request for every query and shows stale results after rapid typing. How do you tie work to state?
asyncswiftconcurrency - 23
A delegate API can call success, failure, or never call back after cancellation. How do you bridge it with continuations?
delegationapiresilience - 24
Swift 6 rejects passing a legacy mutable ImageCache class into two tasks because it is not Sendable. What is the correct fix?
swift - 25
A legacy cache uses a concurrent DispatchQueue with barrier writes and must be called from new async code. Would you replace it with an actor immediately?
asyncconcurrencycaching - 26
A background queue calls DispatchQueue.main.sync while the main thread waits on that background work. Why does the app hang?
concurrencydata-structures - 27
A user-initiated task waits behind 5,000 utility-priority operations on one actor, delaying a tap response by two seconds. How do you fix the priority problem?
concurrency - 28
A Bluetooth callback emits 100 samples per second, but the UI consumes only 10. How do you expose it as AsyncStream without unbounded memory?
asynccallbacksconcurrency - 29
A profile screen needs account, permissions, and avatar concurrently, while a gallery loads a variable number of albums. Which concurrency primitives fit?
concurrency - 30
A ProMotion feed targets 120 Hz but p95 frame time is 14 ms and users see hitches. What budget and tools do you use?
- 31
Changing one unread-count property causes a SwiftUI dashboard with 12 cards to recompute every card body. How do you narrow invalidation?
swiftuiswift - 32
A feed downloads 12-megapixel JPEGs for 120 by 120 point thumbnails and reaches 900 MB memory. How do you redesign image handling?
memory - 33
Applying a diffable snapshot for 10,000 items blocks the main thread for 180 ms even though only 20 rows changed. What do you change?
snapshotconcurrency - 34
Cold launch on an iPhone three generations old grows from 1.2 to 2.9 seconds after adding analytics and feature SDKs. How do you recover the budget?
- 35
A reusable UIKit card creates 70 Auto Layout constraints and layoutSubviews takes 9 ms for each visible cell. What do you inspect?
uikitlayoutautolayout - 36
A screen feels slow, but Time Profiler shows several small costs and no obvious 100 ms function. How do you find the critical path?
schedulingcommunication - 37
An @Observable app model contains 40 properties and is injected into every screen through Environment. What state boundary would you create?
- 38
A parent creates an @Observable editor model, and three child SwiftUI views must edit its fields without creating copies. Which wrappers do you use?
swiftuiswift - 39
A Combine type-ahead search issues five requests for five keystrokes and sometimes displays the first response last. Which operators fit?
combine - 40
A Combine pipeline receives 500 sensor values per second, performs a 4 ms transform per value, and falls behind. How do you handle pressure?
soft-skillsci-cdcombine - 41
A background Core Data import passes NSManagedObject instances to MainActor and intermittently crashes. What crosses the boundary instead?
core-data - 42
Importing 100,000 Core Data records takes 90 seconds and grows memory to 1.1 GB. How do you reduce both?
core-datamemory - 43
A new offline notes feature targets iOS 17+, stores 50,000 records, and may need CloudKit later. Would you choose SwiftData or Core Data?
core-dataswift - 44
A news app fetches the same 2 MB feed every launch even when the server returns cacheable ETag headers. How do you configure URLSession caching?
cachingconfignetworking - 45
A payment request times out after the server may have charged the card. Should the URLSession client retry it automatically?
resiliencenetworking - 46
A 600 MB video upload must continue after the app is suspended or terminated. Which URLSession design fits?
designnetworking - 47
Twenty requests receive 401 simultaneously when an access token expires, causing 20 refresh calls and repeated failures. How do you serialize refresh?
tokensserialization - 48
An async view-model test uses Task.sleep and fails intermittently on CI. How do you make time and completion deterministic?
asyncconcurrency - 49
A reducer starts a request on appear, cancels it on dismiss, and must ignore a late response. What tests prove the behavior?
testing - 50
A Core Data repository and URLSession client must be tested without a real server or a persistent user store. What test boundaries do you build?
core-datanetworking - 51
An image feed starts at 180 MB and reaches 720 MB after 12 minutes of scrolling, then receives a memory warning. How do you find and stop the growth?
memory - 52
Every presentation of a checkout screen leaves another 18 MB alive, and memory grows by 180 MB after ten dismissals. How do you locate the retain cycle?
memory - 53
After users watch 20 videos, 20 AVPlayer instances and 40 player items remain alive even though every screen was dismissed. What do you inspect?
- 54
A background import of 120,000 records peaks at 1.6 GB and is killed on 3 GB devices, but Instruments shows few long-lived domain objects. How do you reduce memory?
memoryprofiling - 55
A SwiftUI detail flow adds 25 MB each time users push and pop it, while the view structs disappear but the feature store does not. How do you debug it?
swiftmlopsswiftui - 56
Opening and closing a support chat five times leaves five WKWebView instances and adds 90 MB per visit. What is the likely ownership trap?
ownership - 57
A C image library adds about 6 MB per export, but Swift Memory Graph shows no retaining Swift objects. How do you investigate?
memoryswift - 58
Crash-free users fall from 99.92% to 98.7%, and the top crash is EXC_BAD_ACCESS in objc_retain with hexadecimal frames. What is your first analysis sequence?
- 59
A force unwrap in receipt parsing causes 34% of all crashes, but only for one locale and invoices older than 2022. How do you fix and verify it?
- 60
An Array index out of range crash occurs 70 times per million sessions while a diffable list receives background updates. How do you reproduce it?
indexessessions - 61
A release crashes only on iOS 16 with unrecognized selector after adding a framework built with a newer SDK. How do you isolate the compatibility fault?
- 62
Crash reports show a stack overflow after a settings change, with 1,400 repeated observer frames. How do you break the recursion?
recursionmemory - 63
The app disappears under image editing with no normal crash stack, and MetricKit reports peak memory near 2.1 GB. How do you confirm an out-of-memory termination?
memorymonitoring - 64
MetricKit reports watchdog terminations during launch, and the main-thread sample shows a synchronous 18-second database migration. What do you change?
databasemigrationsmonitoring - 65
Tapping Pay freezes the UI for 1.8 seconds, but the request itself takes only 120 ms. How do you find the main-thread blocker?
communicationconcurrency - 66
A 120 Hz feed shows p95 frame time of 22 ms after a new image effect ships. How do you identify the jank source?
- 67
One typing event makes a SwiftUI form with 60 fields recompute for 45 ms and drops visible keystrokes. How do you narrow the update?
formsswiftuiswift - 68
Rotating a complex UIKit dashboard blocks the main thread for 320 ms and logs unsatisfiable constraints. What do you inspect first?
uikitautolayoutconcurrency - 69
Opening History blocks scrolling for 260 ms while Core Data fetches 40,000 rows on the view context. How do you restore responsiveness?
responsivecore-data - 70
A card expansion animation runs at 30 FPS on older devices after adding shadows and masks. How do you decide what to simplify?
- 71
Touches stop responding for 900 ms whenever an analytics event is sent, even though dispatch to its SDK appears asynchronous. How do you prove the hidden wait?
asyncconcurrency - 72
After migrating callbacks to async/await, a shared Dictionary crashes with EXC_BAD_ACCESS about once per 200,000 requests under load. How do you fix the race?
asynccallbacksconcurrency - 73
An Auth actor checks that a token is expired, awaits refresh, and three callers each overwrite state with different refresh results. What went wrong?
asyncconcurrencytokens - 74
A continuation bridge crashes in debug with 'tried to resume more than once' when cancellation races a delegate callback. How do you repair it?
callbacksprotocolsresilience - 75
A Core Data object created in a background task is read on MainActor and crashes only during heavy sync. How do you remove the concurrency violation?
core-dataconcurrency - 76
A Task.detached updates an ObservableObject's @Published state and causes rare 'Publishing changes from background threads' failures. What change do you make?
concurrency - 77
An AsyncStream wrapper continues delivering location updates after its consumer is cancelled, and each reopened screen doubles callbacks. How do you stop it?
asynccallbacksconcurrency - 78
During a staged GCD-to-actor migration, one path uses queue.sync and another awaits the actor, producing a rare deadlock. How do you unwind the hybrid design?
asyncdata-structuresconcurrency - 79
Cold launch regresses from 1.4 to 3.2 seconds after three SDKs are added. How do you recover time to first frame?
- 80
The first frame appears in 1.2 seconds, but the home screen cannot be used until a 4.5-second configuration request completes. How do you improve TTI?
config - 81
After a release, daily battery use rises by 35% and background traffic triples for users with notifications enabled. How do you isolate it?
- 82
API p95 grows from 400 ms to 1.8 seconds after retry logic ships, while server latency is unchanged. What client behavior do you inspect?
resilienceapilatency - 83
The app archive grows by 86 MB and clean launch slows by 280 ms after adding two binary frameworks. How do you decide whether to keep them?
- 84
An on-device image classifier holds CPU near 85%, heats the phone, and slows from 40 to 12 frames per second after three minutes. How do you respond?
zero-to-one - 85
Core Data sync creates 6,000 duplicate contacts after two devices reconnect, even though each contact has a server ID. How do you repair the model and data?
core-data - 86
Two devices edit the same SwiftData note offline, and reconnecting silently loses one user's title change. How do you define conflict handling?
swift - 87
A Core Data migration fails for 0.8% of users with stores created four app versions ago. What do you do before shipping another attempt?
migrationscore-data - 88
An offline cart shows 5 items, but the server accepts only 3 after reconnect, and the badge continues to display 5. How do you reconcile state?
- 89
A Combine settings screen sometimes shows an older profile after two save requests finish out of order. How do you prevent the rollback?
combinerollback - 90
A background Core Data save succeeds, but the visible list remains stale until app restart for 7% of sync sessions. What do you inspect?
core-datasessions - 91
After users install iOS 18, a UIKit navigation flow dismisses its sheet twice and lands on the wrong screen in 4% of sessions. How do you isolate the OS regression?
uikitnavigationsessions - 92
A multi-window iPad app presents alerts on the wrong scene because legacy code uses UIApplication.shared windows. How do you replace the deprecated assumption?
alerting - 93
An App Store submission is blocked because a newly updated analytics SDK lacks required privacy-manifest declarations. What is your technical response?
- 94
After an iOS update, users with limited Photos access see an empty picker even though they granted access to 12 images. How do you debug it?
- 95
Moving to the Swift 6 toolchain produces 1,300 concurrency errors, and a quick patch adds @unchecked Sendable to 90 classes. What do you do?
concurrencyswift - 96
A UICollectionView self-sizing layout changes after an iOS update, making 8% of message cells jump from 80 to 300 points. How do you contain it?
uikit - 97
A junior engineer fixes a checkout leak by adding weak self to 14 closures, but payment completion now disappears in 3% of slow sessions. How do you mentor them through the correction?
closuresmentoringsessions - 98
A mid-level engineer responds to a data race by marking a mutable cache @unchecked Sendable, and Thread Sanitizer still reports failures. How do you handle the review?
soft-skillscachingvalidation - 99
An engineer tries to fix 120 Hz scrolling by caching every transformed image, reducing hitches but increasing memory from 240 to 850 MB. How do you coach the next iteration?
cachingmemoryiteration - 100
An engineer ships a force-unwrap crash fix by replacing nil with an empty string, but checkout now submits invalid addresses. How do you turn the review into better judgment?