Performance cheat sheet
The 10 Performance interview answers in 30 seconds each, plus quick facts. Printable, free.
One page per topic: print it or save it as PDF from your browser's print menu. Practise these questions
Profiling an iOS app
Reproduce the problem on a real device with a Release build, then use the right Instruments template: Time Profiler for CPU, Hangs and Animation Hitches for UI responsiveness, Allocations and Leaks for memory, App Launch for start-up, and Energy Log for battery. Add os_signpost intervals around your own work so traces show your app's phases. Fix the heaviest stack, then measure again.
Dropped frames and hitches
A frame is dropped (a hitch) when the app or the render server misses the display's deadline: about 16.7 ms at 60 Hz and 8.3 ms at 120 Hz on ProMotion devices. Causes are main-thread work during scrolling or animation (decoding, layout, synchronous I/O) and expensive rendering such as offscreen passes. Move work off the main thread, cache layout, and simplify layers.
Smooth scrolling in lists
Keep cellForRow cheap: reuse cells, configure from precomputed view data, and never decode images or do I/O there. Load and downsample images asynchronously, prefetch upcoming rows and cancel ones that leave. Use estimated sizes with simple constraints, avoid offscreen rendering such as shadows without a shadowPath, and profile while scrolling on an older device.
Offscreen rendering
Some layer effects can't be drawn in a single pass, so the render server draws them into a separate buffer first, which costs GPU time and can cause hitches. Common causes are shadows without a shadowPath, masks, and corner radius with masksToBounds on layers with content in sublayers. Find them with the Simulator's Debug › Color Off-screen Rendered option, then supply a shadowPath, avoid masks, or pre-render static content.
Lazy loading
Lazy loading defers work until it is needed: lazy var properties are created on first access, lazy stacks and lists build rows as they scroll into view, images load when cells appear, and pagination fetches pages as the user nears the end. It lowers launch time, memory and network use.
Reducing app size
Start with Xcode's App Size Report from an exported archive to see what takes space. Put images in asset catalogs so App Thinning delivers only the right variants per device, compress or convert large media, remove unused code, assets and dependencies, and move optional content to On-Demand Resources or Background Assets. Bitcode is deprecated, so it is no longer a size technique.
Fixing a launch time regression
Reproduce on a device with a Release build and measure with the App Launch instrument, comparing against the last good build. Look at the three phases: loading dynamic libraries, static initialisers, and work done before the first frame in app delegate, scene or first view setup. Defer non-critical SDKs and work, reduce dynamic frameworks (or use mergeable libraries), and add a launch test to CI.
Measuring a slow screen without guessing
Define what slow means (for example time from tap to interactive), add OSSignposter intervals for each phase such as network, decoding and layout, and record them in Instruments on real devices. Compare with field data from MetricKit or your analytics. Fix the largest phase, then measure again against a budget and keep a performance test for it.
import os
let signposter = OSSignposter(subsystem: "com.example.shop", category: "Checkout")
func loadCheckout() async throws -> Checkout {
let state = signposter.beginInterval("LoadCheckout")
defer { signposter.endInterval("LoadCheckout", state) }
async let cart = api.cart()
async let methods = api.paymentMethods()
return try await Checkout(cart: cart, methods: methods)
}A 50,000-item list that stutters on updates
Profile the update itself. Look for sorting, filtering or diffing on the main thread, identifiers that are unstable or expensive to hash, and full reloads where a few items changed. Compute new data off the main actor, use stable ids with diffable snapshots and reconfigure only changed items, and batch rapid updates. In SwiftUI, check the ForEach ids and keep row views cheap.
From a tap to a pixel on screen
The touchscreen reports a touch; the system routes it to your app's event queue; UIApplication delivers it to the window, which hit-tests to find the view and runs gesture recognizers. Your handler updates state, which marks layout and display as needed. At the end of the run loop turn, Core Animation commits the transaction to the render server, which renders the layer tree on the GPU, and the display shows the frame at the next refresh.
Quick facts
- MetricKit
- Delivers daily metrics and diagnostics (hangs, crashes, launch time, battery) from real users' devices.
- App Thinning
- The App Store delivers only the device's variant of assets (slicing) and can host on-demand resources.
- FormatStyle
date.formatted(.dateTime.day().month())caches formatters for you, unlike creating DateFormatters per call.