SwiftQA
Swift and iOS interview questions, answered properly.
Concurrency
14Actors, isolation, Sendable, structured concurrency, and the Swift 6 rules that trip people up.
- Swift 6, can @MainActor do async work? Yes. Actor isolation controls where synchronous code runs, not whether it can suspend, so a @MainActor function can be async and await freely.
- Task vs Task.detached, and what is actor reentrancy? Task inherits the caller's actor context while Task.detached does not, and actors are reentrant at await points, so state can change mid-suspension.
- What does @concurrent do? @concurrent forces a function onto the background executor, opting out of Swift 6.2's new default where nonisolated async inherits the caller's actor.
- What is nonisolated(nonsending)? nonisolated(nonsending) keeps an async function on its caller's actor instead of hopping to the global executor, avoiding extra Sendable requirements.
- What is the difference between sending and consuming? consuming governs local ownership and lifetime on a single thread, while sending proves a value has no other references so it can safely cross actors.
- How does Swift enforce (or not enforce) thread safety? Swift proves data-race freedom at compile time through actor isolation and Sendable checking, but it can't catch reentrancy bugs or @unchecked escapes.
- What are Swift concurrency isolation rules? Two invariants govern isolation: you can't touch actor-isolated state off that actor, and you can't move a value across domains unless it's Sendable.
- Why is Sendable important? Sendable is how the compiler proves a value can cross isolation boundaries safely, turning whole classes of data race into compile-time errors.
- How does ARC interact with Swift concurrency? ARC has always used atomic retain and release since objects could cross threads, but it only keeps memory valid — Sendable is what makes contents safe.
- When do you still use GCD over async/await? GCD still earns its place for legacy callback APIs, DispatchGroup's heterogeneous waiting, and precise QoS control, not to dodge Sendable errors.
- How do you handle long-running tasks? Foreground work gets a Task with cancellation checks and progress reporting, while backgrounding needs BGTaskScheduler or a background URLSession.
- How do you prevent deadlocks? Avoid blocking a thread while holding a lock, never mix semaphores with async/await, and keep lock ordering consistent across call sites.
- How do you manage Core Data concurrency? Every NSManagedObjectContext is confined to its own queue, so access goes through perform, and objects cross contexts only by NSManagedObjectID.
- What is priority inversion? Priority inversion happens when a high-priority task waits on a lock held by a low-priority one that medium-priority work keeps preempting.
Architecture
14Patterns, modularization, dependency inversion, and the problems that only surface on large teams.
- Why did you choose this architecture? A strong answer names the actual constraints that drove the choice, what alternative was considered and rejected, and what you would do differently now.
- MVVM vs MVC vs TCA vs Clean Architecture: pros and cons MVC is fast but overloads the view controller, MVVM balances testability and ceremony, and TCA or Clean Architecture demand steep learning for real payoff.
- What are coordinators? Coordinators are plain objects that own navigation decisions, decoupling screens from each other and making flow logic testable outside a view controller.
- What architectural problems appear only at scale? Small apps mostly need code written correctly and fast, while large ones face coordination cost across teams, build times, and migrations.
- How do you prevent massive ViewModels? Massive ViewModels come from having nowhere else to put logic, so the fix pushes business rules into services and navigation into coordinators.
- How do you structure modules in large iOS apps? Large apps combine horizontal layers like Core and Networking with vertical feature modules that depend downward on those layers but never on each other.
- What is Clean Architecture and how have you applied it? Clean Architecture's dependency rule keeps source code pointing inward toward business logic, with outer layers implementing protocols inner layers define.
- How do you isolate features in multi-team apps? Multi-team isolation mirrors module boundaries to team ownership, shares protocol-defined contracts, and gates rollout with feature flags.
- How do you share business logic safely? Safe sharing means exposing a narrow, protocol-defined contract instead of a concrete implementation, tested independently and versioned like a public API.
- How do you avoid tight coupling? Tight coupling comes from depending on concrete types, singletons, or wide interfaces, so inject the narrowest protocol that covers what's needed.
- What is dependency inversion in iOS? Dependency injection just passes a dependency in, while inversion requires both sides to depend on an abstraction the high-level code effectively owns.
- How do you design a production networking layer? A production networking layer hides URLSession behind an HTTPClient protocol, models requests declaratively, and serializes token refresh through an actor.
- How do you manage multiple environments? Xcode configurations and schemes with distinct bundle identifiers separate Debug, Staging, and Production, centralized into one AppEnvironment type.
- How do you design app-wide error handling? Each layer should define errors meaningful to itself, translate deliberately at its boundary, and funnel presentation through one error-to-UI mapping.
Generics, existentials, ARC, dispatch, ABI stability, and what Swift actually does under the hood.
- How does Swift ABI stability affect app distribution and binary compatibility? ABI stability, shipped in Swift 5, let Apple embed the Swift runtime in the OS itself, shrinking binaries and enabling cross-compiler binary frameworks.
- How does Swift handle memory for value types internally? Small structs copy on the stack for free, while Array, Dictionary, and String defer the real copy through copy-on-write until a shared buffer gets mutated.
- How do generics work under the hood in Swift? Generics compile to one shared implementation dispatched via witness tables and metadata, unless the compiler sees the concrete type and specializes it.
- What are opaque return types (some) and why were they introduced? some hides a function's concrete return type from callers while the compiler still knows it exactly, avoiding existential boxing costs entirely.
- When would you use Any or AnyObject, and why should they be avoided? Any and AnyObject erase type information and cost heap boxing past 24 bytes, so they belong at real boundaries like JSON parsing or Objective-C interop.
- What are property wrappers and how have you used them in production? Property wrappers encapsulate storage and behavior behind @propertyWrapper, and projectedValue exposes a secondary value, the way SwiftUI's @State does.
- What are existentials and why are they expensive? An any Protocol value sits in a 3-word container that heap-allocates past 24 bytes, and every call goes through witness-table indirection.
- How does protocol dispatch differ from dynamic dispatch? Witness-table dispatch for protocols and vtable dispatch for classes are both dynamic, but vtables model a hierarchy, witness tables model conformance.
- What is @frozen and why does it matter for enums? @frozen locks a type's shape forever so clients can drop @unknown default and the compiler can inline its layout, at the cost of never adding a case later.
- How does Swift bridge with Objective-C? Bridging headers expose Objective-C to Swift, generated -Swift.h headers expose @objc types back, and _ObjectiveCBridgeable converts String and Array.
- How does Swift optimize enums with associated values? Swift packs an enum's case tag into spare bits of its payload when possible, so Optional of a class reference ends up exactly the size of a raw pointer.
- What happens at compile time vs runtime in Swift? Type checking, Sendable verification, and static dispatch resolve at compile time, while ARC counts and existential lookups happen at runtime.
- When are unsafe pointers justified? Unsafe pointers earn their keep at C interop, raw memory reinterpretation, or hot paths where profiling proves bounds-checking is the bottleneck.
- When would you still write Objective-C today? New Objective-C is still justified in an existing large codebase or for runtime dynamism like swizzling and heavy KVO, not for new greenfield work.
- What is the difference between a class and a struct and when would you use them? Structs are value types and copy on assignment; classes are reference types and share instances. Default to a struct until you need identity.
Instruments, memory leaks, retain cycles, launch time, and finding regressions before users do.
- How do you detect memory leaks in production? Production leak detection works from symptoms like memory telemetry and MetricKit's jetsam data, narrowing the search before Instruments confirms it.
- Which Instruments do you use most, and why? Time Profiler for CPU, Allocations for memory growth, and Leaks for cycles. Which one you reach for depends on the symptom you are chasing.
- How do you debug retain cycles at scale? Cycles at scale often span modules nobody wrote end to end, so telemetry narrows the search and the Memory Graph Debugger traces the actual chain.
- How do you profile CPU vs memory? Time Profiler's sampled call stacks show where CPU time goes, while Allocations tracks what stays alive, and the two meet around allocation churn.
- How do you design image caching? A two-tier design pairs NSCache for fast memory access with a size-capped disk cache, downsampling images via ImageIO and decoding off the main thread.
- How do you detect performance regressions? Detecting a regression needs a baseline first, so XCTest performance tests in CI, MetricKit data, and version-sliced telemetry all watch for a step change.
- How do you optimize app launch time? Launch time splits into pre-main, main to first frame, and first frame to interactive, each needing its own fix, from fewer frameworks to deferred setup.
SwiftUI
6Navigation state, re-render debugging, UIKit interop, and building components that scale.
- How do you mix UIKit and SwiftUI safely? UIHostingController embeds SwiftUI in UIKit and UIViewRepresentable embeds UIKit in SwiftUI, but the real discipline is one clear owner for shared state.
- What are SwiftUI's current limitations? Sheet-presentation keyboard focus timing, rich text editing, and exotic gesture composition with Canvas still lag native UIKit and AppKit in 2026.
- How do you debug unexpected SwiftUI re-renders? Self._printChanges() reveals which property triggered a body re-evaluation, and a broadly-scoped observable object is usually the real cause of the churn.
- How do you manage navigation state in SwiftUI? NavigationStack turns navigation into state via a NavigationPath owned by a router, though its type erasure often pushes teams toward a plain Route array.
- How do you design reusable SwiftUI components? Reusable components lean on ViewBuilder for structure and custom ButtonStyle or ViewModifier types for optional behavior, extracted only when needed.
- How do you avoid unnecessary view updates? Giving each view the smallest dependency surface, via @Observable's field tracking and passing specific fields instead of whole objects, cuts updates.