Explain zip, merge, and switchMap. How do they combine or select among streams, and what are their typical use cases?
answer
- zip = pair by index, wait for all, max latency
- zip empty source -> empty result
- merge = interleave concurrently by timing
- concat/mergeSequential = ordered alternatives
- switchMap = cancel previous, latest wins (autocomplete)
basics
~20 szip pairs one item from each source into a combined tuple, emitting when all have a value. merge interleaves items from multiple sources concurrently as they arrive. switchMap maps each element to a Publisher but cancels the previous inner when a new element arrives, keeping only the latest.
solid answer
~40 sThese are combination/selection operators. zip (Flux.zip / Mono.zip) waits for one element from each source and combines them index-by-index into a tuple or via a combinator; it completes when the shortest source completes, so it's for correlated joins like parallel independent calls whose results you need together. merge (Flux.merge) subscribes to all sources eagerly and interleaves their emissions in real time as they arrive — order is by timing, not source; use it to fan multiple event streams into one. mergeSequential concatenates in subscription order after subscribing eagerly. switchMap maps each source element to an inner Publisher but cancels/unsubscribes the previous inner whenever a new source element arrives, so only the latest inner survives — ideal for search-as-you-type or 'latest wins' where stale in-flight work should be dropped.
code
java · 12 lines// zip: parallel independent calls joined together (latency = max of the two)
Mono<Profile> profile = Mono.zip(
userService.findById(id), // Mono<User>
orderService.recentFor(id)) // Mono<List<Order>>
.map(t -> new Profile(t.getT1(), t.getT2()));
// merge: interleave two live streams as they emit
Flux<Event> combined = Flux.merge(priceStream, tradeStream);
// switchMap: search-as-you-type; each new query cancels the previous one
Flux<Results> results = keystrokes
.switchMap(term -> searchService.query(term)); // only latest term survivesgo deeper
Likely only knows zip pairs values; merge/switchMap nuances are beyond junior scope.
Should distinguish zip (wait for all) from merge (interleave) and name a use case each.
Core target: explain switchMap cancellation, zip's shortest-source completion and empty-source pitfall, merge vs concat vs mergeSequential.
Reasons about cancellation side-effects, back-pressure to slowest source in zip, and choosing combinators to bound latency and resource use in aggregation endpoints.
## zip — correlate by index `Flux.zip(a, b)` / `a.zipWith(b)` / `Mono.zip(...)` takes one element from **each** source and combines them positionally: 1st-of-a with 1st-of-b, 2nd with 2nd, etc. It **waits** until every source has produced the needed element before emitting a combined result (a `Tuple2`, or a custom value via a combinator function). - **Completion:** completes when the **shortest** source completes; extra elements from longer sources are dropped. - **Back-pressure:** paces to the slowest source. - **Classic use:** run several **independent** async calls in parallel and join their results — e.g. `Mono.zip(userMono, ordersMono, prefsMono)` to assemble one aggregate response. Both inner Monos subscribe concurrently, so total latency is the **max**, not the sum. ```java Mono.zip(userService.find(id), orderService.recent(id)) .map(t -> new Profile(t.getT1(), t.getT2())); ``` ## merge — interleave by time `Flux.merge(a, b, ...)` subscribes to **all** sources **eagerly and concurrently**, and emits each element **as soon as it arrives**, regardless of source. Output order reflects **timing**, not source order. - **vs concat:** `Flux.concat` subscribes to sources one-at-a-time in order (source-1 fully, then source-2). `merge` runs them all at once. - **vs mergeSequential:** subscribes eagerly but preserves subscription order in output. - **Use:** combining multiple live event streams (e.g. two SSE feeds) into one. ## switchMap — latest wins `switchMap(Function<T, Publisher<R>>)` maps each source element to an inner Publisher — but when a **new** source element arrives, it **cancels the current inner subscription** and switches to the new one. Only the most recent inner is ever active. - **Effect:** results from superseded elements are discarded (their in-flight work is cancelled). - **Canonical use:** **type-ahead / autocomplete search** — each keystroke starts a query; a newer keystroke cancels the stale query so you only get results for the latest input. Also 'reload latest config' style flows. - **Contrast with flatMap:** flatMap keeps ALL inners alive and merges them; switchMap keeps only the latest. ## Gotchas - **zip drops trailing elements** from longer sources and stalls if one source never emits — a source that emits **empty** makes the whole zip empty (a frequent WebFlux bug: an empty Mono in a zip silently produces an empty result and the handler returns nothing). - **merge** does not preserve order; if you need order use `concat` or `mergeSequential`. - **switchMap** cancellation means side effects in a cancelled inner may be interrupted mid-flight — don't rely on them completing. - `zipWith`/`Mono.zip` with a `Mono` that is `empty()` yields an empty result; guard with `defaultIfEmpty`/`switchIfEmpty`. ## Quick decision guide - Need **all** results together, correlated → **zip**. - Need to **flatten several streams** as events happen → **merge**. - Need only the **latest**, cancel stale work → **switchMap**.
- In a Mono.zip, one of the inner Monos returns Mono.empty(). What does the zip emit?Nothing — the zipped Mono completes empty. zip needs a value from every source, so an empty source makes the whole result empty. Guard each source with defaultIfEmpty or switchIfEmpty if a missing value should still produce output.
- Why is switchMap preferred over flatMap for autocomplete?switchMap cancels the previous inner query when a new keystroke arrives, so stale/slower responses are discarded and you never render results for an outdated term. flatMap would keep all queries alive and could emit older results after newer ones.
saying these in an interview costs you the question
- Thinking merge preserves source order
- Believing zip emits partial results when a source is empty
- Confusing switchMap with flatMap (not knowing it cancels)
- Assuming zip runs sources sequentially (it's concurrent)