iOS · 13 min
SwiftUI interview questions and what they score
SwiftUI interview questions on identity, state ownership, Observation and performance, each with the answer that scores, the trap and the follow-up.
iOS · 12 min read
UIKit interview questions that still appear in iOS loops: lifecycle, layout passes, cell reuse, diffable data sources, and UIKit with SwiftUI.
This guide is for iOS engineers who have spent the last few years in SwiftUI and now face a loop at a company whose app is UIKit, or a mix of both. It gives twelve UIKit interview questions as they are still asked, what each one scores, the answer that scores, and the follow-up. There is a worked example of the cell reuse bug, a bug that tells an interviewer quickly whether you have shipped a large UIKit app.
Big production apps are mostly UIKit or half UIKit, and even a new SwiftUI screen ends up hosted in a UIKit navigation stack, cell or app delegate. The interviewer is checking that you can be productive in the code that exists today. None of the questions below is obscure. They are the fundamentals a senior iOS engineer is expected to know cold, and the tell is not reciting them. It is placing work correctly and naming the bug each one prevents.
What is scored: placement, not the order.
The answer that scores: “loadView creates the view if there is no storyboard or nib. viewDidLoad runs a single time, once the view is loaded and while its bounds can still change: hierarchy, constraints, data source wiring. viewWillAppear runs on every appearance, including coming back from a pushed screen, so it is where stale content refreshes. viewDidAppear takes work that would otherwise fight the transition animation: starting animations, logging the screen view. The disappear pair tears down what the appear pair started.”
The trap: geometry-dependent work in viewDidLoad. The bounds are not final there.
The follow-up: “Where do you set an initial scroll position that needs the real size?” In viewIsAppearing. It is called after viewWillAppear, when the view is already in the hierarchy and its traits and geometry are real. Build with Xcode 15 or newer and it is available back to iOS 13.
What is scored: whether you know layout is a pass that runs many times.
The answer that scores: “layoutSubviews is on the view: override it in a custom view to position subviews when constraints are not enough. viewDidLayoutSubviews is on the view controller: it runs after the controller’s view has laid out its subviews. Both can run many times, on rotation, on a trait change, on any setNeedsLayout. So work there must be cheap and idempotent. I never call layoutSubviews directly; I call setNeedsLayout, or layoutIfNeeded when I need layout now, for example inside an animation block.”
The trap: one-time setup, or a network call, inside a layout callback. It runs again on every pass.
What is scored: whether you have debugged it.
The answer that scores: “It is a constraint solver: I describe relationships as linear equations and the engine produces frames on each layout pass. In code I use anchors and activate constraints in bulk. The two failure modes are ambiguity, too few constraints so the engine picks, and conflict, too many so UIKit breaks one and logs which.”
The trap: forgetting translatesAutoresizingMaskIntoConstraints = false on a view created in code. UIKit generates constraints from its frame that fight yours, and the log fills with conflicts.
What is scored: content hugging and compression resistance, the puzzle interviewers use to check you can reason rather than guess.
The answer that scores: “Hugging is resistance to growing, compression resistance is resistance to shrinking, set per axis. Out of the box, hugging is 250 and compression resistance is 750. With extra space, both views hug at 250, which is ambiguous, so I lower the label’s hugging and the label stretches. With too little space, both resist compression at 750, so if the button’s title must never truncate, I raise the button’s compression resistance and the label truncates first.”
What is scored: the recycling model.
The answer that scores: “The table or collection view keeps a small pool of cells instead of one per row, which keeps memory bounded for long lists. The cost is stale state: whatever the previous item left on the cell, an image, a selected style, an in-flight request, is still there. So I configure the cell fully after dequeue, reset it in prepareForReuse, and cancel any load it started.”
The follow-up: “Is prepareForReuse enough?” No. See the worked example below.
The answer that scores: “Both dequeue cells from a reuse pool. Collection views separate layout from data, and with a list configuration they reproduce table-style lists too, so most new UIKit code standardises on collection views. Compositional layout builds sections from items, groups and sections declaratively, which replaces hand-written flow layout subclasses for grids, lists and horizontal shelves inside a vertical feed.”
What is scored: whether you know what the diff is keyed on.
The answer that scores: “Instead of index-path bookkeeping and performBatchUpdates, I build a snapshot that lists sections and item IDs, apply it, and the data source works out and animates what changed. That removes the classic ‘invalid number of items in section’ crash. The snapshot holds stable identifiers, not the whole model. If it held models, a changed title would look like a delete plus an insert. For content changes on the same identity I call reconfigureItems, and the cell looks the model up by id.”
The follow-up: “What causes broken list animations on either framework?” Unstable or reused ids.
The answer that scores: “Prefetching through UICollectionViewDataSourcePrefetching to start loads for cells about to appear, and cancelPrefetchingForItemsAt to stop them when the user scrolls away. Decode off the main thread and downsample to the size shown. Then the full lifecycle: cancel on reuse, and check identity before applying a result.”
What is scored: whether you understand why scenes exist.
The answer that scores: “The app delegate owns process-level events: launch setup, push token callbacks, background session wake-ups. The scene delegate owns one UI instance: it creates the window and receives that scene’s moves between foreground and background. There are two delegates because on iPadOS one app can have several windows open: one process, one app delegate, many scenes.”
The trap: handling deep links in only one place. On a cold launch the URL arrives in the scene’s connection options; when the app is already running it arrives in scene(_:openURLContexts:). Covering one path and forgetting the other is a bug that ships all the time.
The answer that scores: “First confirm it: a deinit that never runs, or memory that grows every time the screen opens and closes. The Memory Graph Debugger shows the cycle. The usual causes in view controllers are a closure stored on self that captures self strongly, a delegate declared strong, and timers or observers that retain their target.”
The follow-up: “Does a Task started in viewDidLoad leak the controller?” It retains self until it finishes. A long or endless loop inside it keeps the controller alive, so keep the handle and cancel it when the screen goes away.
The answer that scores: “UIHostingController hosts a SwiftUI view inside UIKit, which is how most migrations start: new screens in SwiftUI, pushed from the existing navigation controller. UIViewRepresentable and UIViewControllerRepresentable bring UIKit into SwiftUI, for a camera, a map, or a text view that needs fine control. At the boundary I decide who owns the state, and I keep the representable’s coordinator as the only place that talks to the UIKit delegate.”
What is scored: judgement, not enthusiasm.
The answer that scores: new screens in SwiftUI by default, existing screens moved when there is a reason (a redesign, a bug-heavy screen, a feature that needs it), and the working UIKit left alone where it works: a complex compositional layout, a text editor, a screen that measured faster in UIKit on older devices. Then the cost side: two UI frameworks means two sets of patterns for the team to know. A candidate who says “always” or “never” loses the judgement point.
The follow-up: “Which screen goes first, and how do you know it worked?” Not the busiest, most tangled screen in the app; that puts the most risk on the first attempt, before you have a pattern to reuse. Pick a new feature or a screen the team keeps changing. The UIHostingController stays a thin adapter: dependencies come in through its initialiser, navigation results go back out to the UIKit side. If the SwiftUI model still calls singletons and pushes view controllers on its own, the rewrite changed the pixels and none of the coupling. Keep the old screen behind a flag so you can flip back at runtime. Then compare behaviour, not screenshots: the analytics events fired, VoiceOver labels and actions, navigation, memory. Matching pixels tells you nothing about any of those.
A bug report: “On fast scroll, a photo flashes in the wrong cell and then changes.” The cell already cancels its load in prepareForReuse.
final class PhotoCell: UICollectionViewCell {
private let thumbnail = UIImageView()
private var fetch: Task<Void, Never>?
private var expectedURL: URL?
func show(_ url: URL, using loader: any ImageLoading) {
fetch?.cancel()
thumbnail.image = nil
expectedURL = url
fetch = Task { [weak self] in
let loaded = try? await loader.fetchImage(at: url)
guard let self, !Task.isCancelled, self.expectedURL == url else { return }
self.thumbnail.image = loaded
}
}
override func prepareForReuse() {
super.prepareForReuse()
fetch?.cancel()
fetch = nil
expectedURL = nil
thumbnail.image = nil
}
}This type-checks in Swift 6 language mode against the iOS 17 SDK (checked with Swift 6.3.2), given an ImageLoading protocol with an async fetchImage(at:) method. What an interviewer listens for:
expectedURL == url, is what stops the old image from landing.show, so the previous photo does not show while the new one loads.[weak self], so a cell scrolled away is not kept alive by its own load.The follow-up ladder: put an in-memory cache in front so scrolling back to a URL shows it immediately; key the guard on something other than the URL if the same image can appear in two cells; move the loading into prefetching; add a fade-in without bringing the race back.

iOS · 13 min
SwiftUI interview questions on identity, state ownership, Observation and performance, each with the answer that scores, the trap and the follow-up.

iOS · 16 min
Twelve senior iOS interview questions for 2026: what each scores, a weak and a senior answer, and the follow-up on Swift 6 concurrency, SwiftUI, architecture.

iOS · 11 min
What an iOS technical interview loop contains, what each round scores, and where strong Swift candidates still lose it.