Skip to main content

Swift engineer role-specific interview questions

30 questions on role-specific for swift engineer candidates. Expect prompts such as “Explain ARC and the three most common retain cycle patterns” and “Compare async/await to GCD DispatchQueue for iOS concurrency”, each with a worked answer outline and the follow-ups interviewers push on.

Questions

Showing 1 to 25 of 30 role-specific questions (page 1 of 2).

As asked

How does Automatic Reference Counting work in Swift, and what are the three most common patterns that produce retain cycles in real iOS codebases? Walk me through how you detect and fix each one.

Sample answer outline

A strong answer explains that ARC inserts retain/release calls at compile time rather than runtime, then covers delegate cycles (strong delegate property), closure captures (capturing self strongly inside a stored closure on self), and parent-child object graphs (two objects holding strong references to each other). Candidate should name weak and unowned, explain when each is safe, and mention the Memory Graph Debugger or Instruments Allocations to detect leaking objects.

Expect these follow-ups

  • When is unowned safer than weak, and what crashes can unowned produce?
  • How does capture list syntax change when you have multiple captured variables?
arcretain-cyclememoryswiftinstruments

As asked

You have a codebase that uses DispatchQueue heavily. A colleague wants to migrate to async/await. What are the concrete differences in thread ownership, stack suspension, and error propagation, and what would you actually migrate versus leave alone?

Sample answer outline

A strong answer covers: async/await suspends the logical call stack without blocking a thread (cooperative threading), GCD blocks an OS thread while waiting, structured concurrency with TaskGroup vs. DispatchGroup, and how async/await throws propagates naturally while GCD needs Result callbacks or semaphores. The migration advice should flag callback-heavy networking and URLSession as easy wins, while noting that code relying on DispatchQueue-specific ordering guarantees or C interop may be better left alone.

Expect these follow-ups

  • How does Task cancellation propagate in an async/await tree vs. manual DispatchWorkItem cancellation?
  • What does withCheckedContinuation let you do and what pitfall does the checked variant catch?
concurrencyasync-awaitgcdstructured-concurrencyswift

As asked

A user reports your app's memory climbs every time they navigate to a detail screen and back. Walk me through exactly how you use Xcode's Memory Graph Debugger to find the leak, from launching the app to pinpointing the retain cycle.

Sample answer outline

A strong answer covers: profile in debug build with MALLOC_STACK_LOGGING, navigate the leaking path several times, tap the memory graph icon in the debug bar, filter by class name, inspect the reference graph to find the cycle, hover over edges to see reference kinds (strong/weak), and correlate with the call stack from the allocation backtrace. Candidate should mention that dismissing a view controller that stays alive is the classic symptom.

Expect these follow-ups

  • When would you use Instruments Allocations Heapshot analysis instead of the Memory Graph Debugger?
  • How do you find a closure-based retain cycle in the graph when the closure has no visible name?
memorydebugginginstrumentsretain-cyclexcode

As asked

Why does UIKit require all view updates to happen on the main thread, what actually goes wrong at the framework level if you violate this, and how do you audit a large codebase for accidental background UIKit calls?

Sample answer outline

A strong answer explains that UIKit's internal data structures are not thread-safe (no locks), that concurrent mutations cause crashes or rendering artifacts rather than clean errors, and that the run loop and CoreAnimation transaction commits are tied to the main thread's display link. Auditing options: Main Thread Checker in Xcode diagnostics (catches at runtime), static analysis, or wrapping UIView subclasses with a custom assertion in debug builds.

Expect these follow-ups

  • How does SwiftUI's @MainActor attribute handle this requirement at compile time?
  • Can CoreImage and CoreGraphics rendering happen off the main thread safely?
uikitmain-threadthread-safetydebuggingswiftui

As asked

A label and an image view sit side-by-side in a horizontal stack. Sometimes the label truncates and sometimes the image shrinks. Explain content compression resistance and content hugging priorities and walk me through exactly which priority values you would set to fix this layout.

Sample answer outline

A strong answer defines compression resistance as resistance to being squished below intrinsic size (default 750) and hugging as resistance to growing beyond intrinsic size (default 250). To keep the image at its natural size and let the label compress: raise the image's compression resistance above the label's (e.g. 751 vs 750) or lower the label's. To prevent the image from stretching: raise the image's hugging priority. Candidate should mention that equal priorities cause ambiguity warnings at runtime.

Expect these follow-ups

  • How do you debug Auto Layout constraint conflicts without reading the cryptic runtime log?
  • When would you choose compositional layout over Auto Layout for a complex list?
auto-layoutuikituistackvivewconstraintsios

As asked

You receive a JSON payload where the date is a Unix timestamp integer, nested address fields need to be flattened into your model, and some keys differ from your Swift property names. How do you handle all three with Codable without writing a completely custom init(from:)?

Sample answer outline

A strong answer covers: dateDecodingStrategy = .secondsSince1970 on JSONDecoder for timestamps, CodingKeys enum for key renaming, and a custom init(from:) using a nested container (container.nestedContainer(keyedBy:forKey:)) to flatten nested address without reimplementing all other properties. Candidate should mention AnyCodingKey as a technique for dynamic key names.

Expect these follow-ups

  • How does JSONDecoder handle optional keys that are absent versus keys present with a null value?
  • What would you do if the same JSON field can be either a String or an Int depending on context?
codablejsonnetworkingswiftdecoding

As asked

Explain the practical difference between value and reference semantics in Swift, describe how copy-on-write works for Array and Dictionary, and give an example where accidentally sharing reference semantics caused a subtle bug.

Sample answer outline

A strong answer contrasts struct (copied on assignment, no shared state) with class (reference counted, shared state), explains CoW as the optimization where Array/Dictionary share a buffer until a mutation is attempted then copy, and gives a concrete bug like passing a class containing a mutable array and mutating it in a closure thinking a copy was made. Candidate should know isKnownUniquelyReferenced for implementing custom CoW types.

Expect these follow-ups

  • How does Swift enforce Sendable to prevent value-type escape problems in concurrent code?
  • When would you deliberately choose a class over a struct for a model type?
value-semanticsreference-semanticscopy-on-writeswiftstructs

As asked

Walk me through exactly how SwiftUI's diffing and update mechanism works. When a @State variable changes, what happens under the hood before pixels change on screen?

Sample answer outline

A strong answer covers: @State change triggers an invalidation of the owning view, SwiftUI calls body again to produce a new view value (a value type description, not a real view tree), the framework diffs the attribute graph rather than a view hierarchy, only changed subtrees are committed to the render tree, and layout plus drawing happen on the main thread. Candidate should distinguish the attribute graph from UIKit's layer tree and explain why making body cheap is critical to frame rate.

Expect these follow-ups

  • What is EquatableView and when does it actually help performance?
  • How does @ViewBuilder's result-builder transformation affect the attribute graph?
swiftuistaterenderingattribute-graphperformance

As asked

Explain how Swift actors work at the concurrency level. What guarantees do they provide, what are their limits, and how do you access actor-isolated state from a non-isolated context?

Sample answer outline

A strong answer explains that actors serialize access to their mutable state through an executor, that accessing actor-isolated properties from outside requires await, that the compiler enforces isolation at the call site, and that actors do not protect value types passed in or out (Sendable matters here). Candidate should mention MainActor as a special global actor and the difference between actor reentrancy and true thread safety.

Expect these follow-ups

  • What is actor reentrancy and how can it violate invariants you thought were safe?
  • When would you use a global actor instead of an instance actor?
actorsconcurrencysendablemainactorswift

As asked

What is Swift's Optional type actually? Describe its representation, how the compiler handles optional chaining at the IR level, and what nil-coalescing compiles down to.

Sample answer outline

A strong answer explains Optional is a generic enum with .none and .some(Wrapped) cases, that the compiler can optimize away the discriminant for pointer types using spare pointer bits (making Optional zero-cost for class references), and that optional chaining desugars to if-let bindings with early exits. Nil-coalescing compiles to a branch that returns the wrapped value or the default expression.

Expect these follow-ups

  • Why can Optional<Optional<Int>> exist and when does it cause bugs in practice?
  • What is IUO (implicitly unwrapped optional) and in what generated code does it still appear?
optionalsswifttype-systemcompilerenums

As asked

Explain the difference between a generic function, a function taking a protocol existential (any P), and one returning an opaque type (some P). When would each approach give you better performance or cleaner API?

Sample answer outline

A strong answer distinguishes: generics are monomorphized by the compiler giving static dispatch and potential inlining, existentials carry a runtime type box and use dynamic dispatch, opaque types (some P) are reverse generics where the caller does not know the concrete type but the compiler does. Performance preference: generics greater than opaque types for return values greater than existentials in hot paths. API preference flips when you need heterogeneous collections (existentials) or want to hide implementation details.

Expect these follow-ups

  • What is the primary associated type feature (Swift 5.7) and how does it simplify existential constraints?
  • Why did Swift add some View in SwiftUI bodies instead of using an existential?
genericsprotocolsopaque-typesswifttype-system

As asked

Your app has a background import of thousands of objects into Core Data while the main context drives a table view. Walk me through the correct concurrency pattern and what goes wrong when developers get it wrong.

Sample answer outline

A strong answer covers NSManagedObjectContext concurrency types (mainQueueConcurrencyType, privateQueueConcurrencyType), that NSManagedObject instances are not thread-safe and must only be used on their owning context's queue, the parent-child context pattern where a private child imports and saves to the parent main context, and NSFetchedResultsController for reacting to changes. Common errors: passing managed objects across contexts causing crashes or stale data, forgetting to call performBlock.

Expect these follow-ups

  • How do you handle merging changes from a background context in Core Data with NSManagedObjectContextDidSaveNotification?
  • What does NSPersistentContainer simplify compared to manually setting up the stack?
core-dataconcurrencyiospersistencensmanagedobjectcontext

As asked

Compare the @Observable macro introduced in iOS 17 with the earlier ObservableObject protocol. What problem does @Observable solve, and what migration pitfalls should you watch for in an existing codebase?

Sample answer outline

A strong answer explains that ObservableObject publishes changes at the object level (any @Published property change re-renders all subscribers), while @Observable uses per-property observation so only views that actually read a property re-render. Migration pitfalls: removing @Published annotations, @State replaces @StateObject for @Observable types, @ObservedObject/@EnvironmentObject drop the wrapper but the injection pattern changes. Candidate should note iOS 17 minimum deployment requirement.

Expect these follow-ups

  • How does the Observation framework achieve per-property tracking under the hood?
  • Can you use @Observable on a class that also conforms to NSObject, and what are the risks?
swiftuiobservationobservableios17state-management

As asked

Your app's cold launch time is 3.2 seconds and users are complaining. Walk me through the phases of app launch, where time is most commonly lost, and which Instruments template or metric you use to find the bottleneck.

Sample answer outline

A strong answer names pre-main time (dyld loading dynamic libraries, running +load methods, static initializers) and post-main time (application:didFinishLaunchingWithOptions, first frame render). Tools: App Launch instrument or MetricKit. Fixes for pre-main: reduce dynamic frameworks, move +load to +initialize, use fewer singletons with static initializers. Fixes for post-main: defer non-critical work, use background threads, pre-render with snapshot APIs.

Expect these follow-ups

  • How does dyld3/dyld4 closure caching change how you think about framework count?
  • What is the MetricKit MXLaunchMetric and how do you collect it in production?
performancelaunch-timeinstrumentsdyldios

As asked

You need to fetch data for fifty user profiles in parallel, but you want at most ten requests in flight at once and the whole operation should cancel cleanly if the enclosing task is cancelled. How do you structure this with Swift's structured concurrency?

Sample answer outline

A strong answer uses withThrowingTaskGroup(of:) adding child tasks, checks task cancellation via try Task.checkCancellation() or isCancelled inside each child, and throttles concurrency by only adding the next task after collecting a result (a semaphore-style approach using the for-await loop over group). Cancellation of the parent task propagates to all child tasks automatically in structured concurrency.

Expect these follow-ups

  • What is the difference between cancellation propagating automatically versus calling cancel() on an unstructured Task?
  • How would you add a timeout to the entire group without cancelling tasks that already finished?
structured-concurrencytaskgroupasync-awaitcancellationswift

As asked

A junior engineer stores an OAuth access token in UserDefaults because it is simple. What security risks does that introduce on iOS, and what would you use instead? What Keychain access control options would you set for a token that should not be accessible when the device is locked?

Sample answer outline

A strong answer notes UserDefaults is a plist stored in the app sandbox without encryption, accessible in unencrypted device backups and in principle readable by tools on a jailbroken device. Keychain items are encrypted by the Secure Enclave keying infrastructure and have data protection classes. For a token that should be inaccessible when locked: kSecAttrAccessibleWhenUnlockedThisDeviceOnly with kSecAccessControlBiometryCurrentSet or UserPresence. Candidate should mention SecItemAdd/SecItemCopyMatching API.

Expect these follow-ups

  • What happens to Keychain items when a user uninstalls and reinstalls your app?
  • How would you rotate a stored token without a window where the user is logged out?
securitykeychainiosauthenticationdata-protection

As asked

Walk me through how you write XCTestCase tests for a function marked async throws in Swift. How do you test error paths, timeout behavior, and actor-isolated code?

Sample answer outline

A strong answer covers marking test functions async throws (XCTest handles the expectation pump automatically), using XCTAssertThrowsError for error paths (noting you need the async variant via do/catch with XCTFail), and testing actor-isolated code by calling await actor.method() directly. For timeout behavior: wrap with Task { try await Task.sleep(nanoseconds:) } or use the newer swift-testing #expect(throws:) API. Candidate should mention that older await-in-test patterns using XCTestExpectation are still valid but verbose.

Expect these follow-ups

  • How would you test code that uses NotificationCenter or system callbacks with async/await?
  • What is the swift-testing framework and how does its API differ from XCTest for async assertions?
testingxctestasync-awaitswiftunit-testing

As asked

A scroll view is dropping frames. Describe step-by-step how you use the Time Profiler instrument to find what is burning CPU, how you read the call tree, and how you separate your own code from system frameworks.

Sample answer outline

A strong answer covers: profile on device (not simulator), use Time Profiler with the Display template to correlate with frame drops, look for the highest self-time entries in the call tree, enable Hide System Libraries to isolate app code, and look for cell reuse path, image decoding, or JSON parsing as typical culprits. Candidate should mention the Core Animation instrument alongside Time Profiler to see commit and render times separately.

Expect these follow-ups

  • What is the difference between self time and total time in the Time Profiler call tree?
  • How would you reduce off-screen rendering (offscreen pass) shown in the Core Animation instrument?
performanceinstrumentscpuscrollviewprofiling

As asked

Explain the UIScene lifecycle introduced in iOS 13, how it differs from AppDelegate lifecycle, and what you must implement to support multiple windows on iPadOS.

Sample answer outline

A strong answer explains that UIScene decouples the concept of a UI session from the process: multiple scenes can exist in one process, each with its own SceneDelegate and lifecycle (willConnectTo, sceneDidBecomeActive, sceneWillResignActive, etc.). For multi-window on iPad: configure UIApplicationSceneManifest in Info.plist with Enable Multiple Windows, implement application(_:configurationForConnecting:options:) in AppDelegate to supply a scene configuration, and restore scene state via NSUserActivity. AppDelegate now handles process-level events, SceneDelegate handles UI-level.

Expect these follow-ups

  • How do you share data between multiple scene instances without a singleton?
  • What happens to state restoration if the user force-quits a specific scene on iPadOS?
ios13scene-delegateapp-lifecycleipadmulti-window

As asked

A user starts a large file download, then backgrounds the app and eventually it is suspended. How does URLSession background session keep the download running, how does your app get notified when it completes, and what are the constraints?

Sample answer outline

A strong answer covers: URLSession with background configuration hands the download to the OS daemon (nsurlsessiond), the app is suspended or even terminated, completion wakes the app via application(_:handleEventsForBackgroundURLSession:), the app recreates the session with the same identifier to drain the event queue, and calls the completionHandler. Constraints: no streaming, only file downloads/uploads, limited on cellular without allowsCellularAccess, and background tasks are at OS discretion.

Expect these follow-ups

  • What happens if the OS delivers the background event while the app is not running and you have not recreated the session?
  • How do you test background URL session behavior in Xcode without waiting for the real OS scheduler?
networkingurlsessionbackgroundiosfile-download

As asked

You have a ViewModel that exposes a Combine publisher. How do you write a deterministic XCTest that verifies the sequence of output values and completion without relying on real time or real network calls?

Sample answer outline

A strong answer uses XCTestExpectation with a sink subscriber, collects emitted values into an array and fulfills the expectation on the expected count, and waits with a timeout. For determinism: inject a mock publisher via protocol or PassthroughSubject controlled by the test, use ImmediateScheduler or TestScheduler from CombineSchedulers to avoid real time. Candidate should mention cancelling subscriptions in tearDown to avoid interference.

Expect these follow-ups

  • How do you test a publisher that uses debounce or throttle without sleeping in your test?
  • What is the CombineExpectations library and what does it add over raw XCTestExpectation?
testingcombinexctestviewmodelswift

As asked

You are setting up Xcode Cloud for a team that has unit tests, UI tests, and needs beta distribution to TestFlight. What workflows would you create, what build settings pitfalls should you watch for, and how do you handle secrets like API keys?

Sample answer outline

A strong answer describes at minimum: a PR workflow that builds and runs unit tests on every pull request, a nightly workflow that runs full UI tests and distributes to TestFlight internal group. Pitfalls: mismatched provisioning profiles, hardcoded bundle IDs in test targets, UI tests that depend on device state. Secrets: use Xcode Cloud environment variables (never commit .env files), inject at build time with build scripts using xcconfig references.

Expect these follow-ups

  • How would you parallelize a 15-minute UI test suite across multiple simulated devices in Xcode Cloud?
  • What is xcodebuild -resultBundlePath and why does it matter for flaky test diagnosis?
ci-cdxcode-cloudtestingdistributiondevops

As asked

Explain the Sendable protocol in Swift's concurrency model. What does it guarantee, how does the compiler check conformance, and what are the escape hatches like @unchecked Sendable and when are they acceptable?

Sample answer outline

A strong answer defines Sendable as a marker protocol indicating a type is safe to share across concurrency domains, that the compiler performs semantic checks on struct members (all must be Sendable), and that classes need manual declaration because the compiler cannot verify their internals. @unchecked Sendable is appropriate for types that are thread-safe internally via locks (like dispatch queue-protected wrappers) but should be narrowly used and documented. @Sendable on closures prevents capturing non-Sendable state.

Expect these follow-ups

  • How does the strict concurrency checking level in Swift 6 change what the compiler reports?
  • Can a class with a NSLock-protected mutable property safely conform to @unchecked Sendable?
sendableconcurrencyactorsswift6thread-safety

As asked

Your iOS app has a cold launch time over five seconds on older devices. How would you diagnose and reduce it?

Sample answer outline

Measure first with Instruments and real devices, separating pre-main time from work after application launch. Common wins include reducing dynamic frameworks, delaying non-critical SDK initialisation, moving network and database work off the launch path, and making the first screen render from cached state. The answer should mention that AppDelegate and scene setup often become dumping grounds for analytics, feature flags and dependency graph construction. Optimisation should preserve correctness by defining what the user must see at first interactive frame. Candidates trip up by optimising isolated functions without proving they affect launch metrics.

Expect these follow-ups

  • What is pre-main time and why can dynamic frameworks hurt it?
  • Which launch work is safe to defer?
  • How would you prevent launch regressions in CI?
performancelaunch-timeios

As asked

When Swift dispatches a method call through a protocol existential, how does it find the implementation? Compare this to class vtable dispatch and explain when each has a meaningful performance cost.

Sample answer outline

A strong answer describes protocol witness tables (PWT) stored alongside existential containers, explains the five-word existential container layout (a three-word inline value buffer, one value-witness table pointer, one protocol witness table pointer), contrasts this with vtable dispatch through a class isa pointer, and notes that existentials on large value types cause heap allocation because they do not fit in the three-word buffer. The performance nuance is that both are indirect calls but existentials add indirection through the container and can inhibit specialization, which matters in tight loops.

Expect these follow-ups

  • What does the any keyword do to protocol existentials in Swift 5.7 and why was it introduced?
  • How does the compiler eliminate witness table dispatch when it can specialize a generic function?
protocolsdispatchgenericsperformanceswift-internals