Combine cheat sheet
The 12 Combine 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
What Combine is for
Combine is Apple's framework for values that change over time. Publishers emit values, operators transform and combine them, and subscribers receive them, with cancellation built in. It shines when several streams must be combined, like form validation or search, and it powers ObservableObject's @Published. For one-shot async work, async/await is now simpler.
Publishers, operators and subscribers
A Publisher declares an Output and a Failure type and delivers values and then optionally one completion (finished or failure). Operators are methods that return new publishers, such as map, filter and debounce. Subscribers like sink and assign request values and receive them. Subscribing returns an AnyCancellable that keeps the subscription alive.
NotificationCenter.default
.publisher(for: UIResponder.keyboardWillShowNotification)
.compactMap { ($0.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect)?.height }
.receive(on: RunLoop.main)
.sink { [weak self] height in self?.bottomInset = height }
.store(in: &cancellables)AnyCancellable and subscription lifetime
Subscribing returns an AnyCancellable, which cancels the subscription when it is deallocated. Keep it alive for as long as you want values, usually in a Set<AnyCancellable> owned by the object, with .store(in: &cancellables). When the owner is freed, its subscriptions are cancelled too, as long as the sink closures don't strongly capture the owner.
debounce vs throttle
debounce waits until values stop arriving for a set time and then emits the last one, which suits search-as-you-type. throttle emits at most one value per interval while values keep arriving, either the first or the latest, which suits scroll or sensor updates. Debounce can delay forever while input keeps coming; throttle guarantees regular output.
combineLatest
combineLatest waits until every upstream has emitted at least once, then emits a tuple of the latest value from each whenever any of them emits. It is ideal for state derived from several inputs, such as enabling a button when all form fields are valid.
merge vs zip
merge interleaves values from publishers of the same type into one stream as they arrive. zip pairs values by position: the first of each, then the second of each, so it waits for every upstream to emit before producing a tuple. Use merge for 'any of these events'; use zip to wait for matching results, such as two parallel requests.
Error handling in Combine
A failure is a completion: once a publisher fails, it ends and sends nothing more. Operators like catch, replaceError and retry handle it, but they replace the failed upstream too. To keep a long-lived pipeline alive, such as search, handle errors inside a flatMap or map + switchToLatest, so only the inner request fails.
eraseToAnyPublisher
Each operator wraps the previous publisher in a new generic type, so a pipeline's real type is long and leaks implementation details. eraseToAnyPublisher() wraps it in AnyPublisher<Output, Failure>, so an API exposes only what it emits and can change its internals freely. Opaque some Publisher also hides the type, but returns one fixed concrete type.
PassthroughSubject vs CurrentValueSubject
Subjects are publishers you push values into with send, bridging imperative code into Combine. PassthroughSubject has no stored value, so late subscribers only see future values; it suits events like taps. CurrentValueSubject stores the latest value, replays it to new subscribers and exposes .value; it suits state.
Testing Combine publishers
Subscribe in the test, collect values, and wait for the expected output. In XCTest use an expectation fulfilled in the sink; with Swift Testing or async tests, iterate publisher.values. For timing operators like debounce, inject a scheduler so tests control time instead of sleeping.
Combine vs async/await
Use async/await for one-shot work like requests and file I/O, and AsyncSequence for simple streams. Choose Combine when you need rich operators over several streams over time, such as combineLatest, debounce or switchToLatest, or when you're working with existing Combine code. With Observation, SwiftUI no longer needs @Published, so new code uses Combine less.
Preventing stale search results
Debounce input, drop duplicates, and make sure only the latest query's response can be shown. In Combine, map each query to a request and use switchToLatest(), which cancels the previous request. In SwiftUI with async/await, .task(id: query) does the same: changing the id cancels the old task. Also ignore empty queries and show a loading state.
Quick facts
- @Published
- Emits in willSet, so reading the property inside the sink gives the old value; use the value passed to the sink.
- Future
- Runs its closure once, immediately on creation, and replays the single result. Wrap in Deferred to run on subscription.
- receive(on:) vs subscribe(on:)
- receive(on:) moves downstream delivery; subscribe(on:) moves subscription and upstream work.