Contrast flatMap, concatMap, and flatMapSequential in terms of ordering and concurrency. When would you pick each?
answer
- Two axes: concurrency + ordering
- flatMap = concurrent, unordered
- concatMap = serial, ordered
- flatMapSequential = concurrent + ordered (buffered)
- default flatMap concurrency 256
basics
~10 sflatMap: concurrent inner subscriptions, output order not preserved. concatMap: one inner at a time (sequential), order preserved, no concurrency. flatMapSequential: concurrent inner subscriptions but output re-ordered to match source order.
solid answer
~40 sAll three map each element to an inner Publisher and flatten. They differ on two axes — concurrency and output ordering. flatMap subscribes to many inner publishers eagerly (default max concurrency 256) and emits results as they arrive, so ordering is not preserved; it's the fastest and best when order doesn't matter. concatMap subscribes to inner publishers one at a time, waiting for each to complete before starting the next, giving strict source order but zero concurrency (and thus higher latency). flatMapSequential is the middle ground: it subscribes eagerly like flatMap (concurrent inner calls) but buffers and re-emits results in the original source order. Pick flatMap for max throughput, concatMap when order matters and side-effect sequencing must be serialized, flatMapSequential when you want concurrency AND ordered output.
code
java · 16 lines// Each inner call takes a variable delay
Function<Integer, Mono<Integer>> slow = i ->
Mono.just(i).delayElement(Duration.ofMillis(100 - i * 10));
Flux.just(1, 2, 3)
.flatMap(slow); // may emit 3, 2, 1 (as they finish)
Flux.just(1, 2, 3)
.concatMap(slow); // always 1, 2, 3 — but runs serially (~sum of delays)
Flux.just(1, 2, 3)
.flatMapSequential(slow); // runs concurrently, emits 1, 2, 3 (buffered to order)
// Bounded concurrency: at most 4 inner calls in flight
Flux.range(1, 100)
.flatMap(id -> webClient.get().uri("/x/{id}", id).retrieve().bodyToMono(String.class), 4);go deeper
Enough to know concatMap keeps order and flatMap doesn't; deeper concurrency nuance is a stretch goal.
Core target: articulate the two axes and map all three operators, plus the concurrency parameter and serial cost of concatMap.
Adds head-of-line blocking of flatMapSequential, DelayError variants, and reasoning about latency = max vs sum.
Discusses tuning concurrency/prefetch, back-pressure propagation to upstream, and choosing operators to protect rate-limited or stateful downstreams.
## The shared shape All three operators have the signature `Function<T, Publisher<R>>`: for each source element you return an inner `Mono`/`Flux`, and the operator flattens the inner emissions into one output stream. They differ ONLY in **how inner publishers are subscribed** and **how their results are ordered**. ## flatMap — eager, interleaved - **Concurrency:** subscribes to multiple inner publishers at once, up to a **concurrency** limit (default **256**, overridable: `flatMap(fn, concurrency)` and `flatMap(fn, concurrency, prefetch)`). - **Ordering:** results are emitted **as they complete**, so a fast inner stream 'overtakes' a slow earlier one. Output order is **non-deterministic** relative to source. - **Use when:** throughput matters and order is irrelevant (e.g., firing N independent enrichment calls). ## concatMap — sequential, ordered - **Concurrency:** subscribes to **one inner publisher at a time**; the next element isn't even mapped until the current inner completes. - **Ordering:** strict **source order** guaranteed. - **Cost:** effectively serial — total latency is the SUM of inner latencies, not the max. - **Use when:** order matters, or inner operations have side effects that must not overlap (e.g., sequential writes, rate-limited APIs, ordered event processing). `concatMapDelayError` defers errors to the end. ## flatMapSequential — eager subscribe, ordered output - **Concurrency:** subscribes **eagerly** like flatMap (inner calls run concurrently). - **Ordering:** buffers completed results and **re-emits them in source order**. So inner #2 may finish before inner #1, but its value is held until #1 emits. - **Cost:** you get concurrency's latency benefit but pay memory to buffer out-of-order results; a slow early element can stall (head-of-line block) later ones from being emitted. - **Use when:** you want both concurrency and ordered output. ## Summary table | Operator | Inner subscription | Output order | Concurrency | |---|---|---|---| | flatMap | eager (up to N) | as-completed (unordered) | high | | concatMap | one at a time | source order | none (serial) | | flatMapSequential | eager (up to N) | source order (buffered) | high | ## Gotchas - People reach for `flatMap` then are surprised results are shuffled — that's by design. - `concatMap` silently serializes and can tank throughput under load; it is NOT just 'ordered flatMap'. - With `flatMapSequential`, a single slow early element causes head-of-line blocking of downstream emission even though the work already ran. - Error handling: `flatMap`/`concatMap`/`flatMapSequential` fail fast by default; the `...DelayError` variants collect errors and emit them after other inners finish. - For `Mono`, the analogous fan-out uses `flatMapMany`. ## Interview framing Name the two axes explicitly (concurrency, ordering) and map each operator onto them. That's what separates a memorized answer from an understood one.
- Your service calls a downstream API that allows only 5 concurrent connections. How do you enforce that with flatMap?Pass a concurrency argument: flatMap(fn, 5). flatMap will keep at most 5 inner subscriptions active, providing natural bounded concurrency / back-pressure to the downstream.
- Why can flatMapSequential still block downstream emission even though it runs concurrently?It buffers out-of-order completions and emits in source order. If element #1's inner is slow, results for #2..#N are computed but held (head-of-line blocking) until #1 emits, increasing memory use and emission latency for the first item.
saying these in an interview costs you the question
- Calling concatMap 'just ordered flatMap' without noting it's serial
- Thinking flatMapSequential runs serially
- Assuming flatMap is unbounded
- Not knowing latency of concatMap is sum, not max