skip to content

Contrast flatMap, concatMap, and flatMapSequential in terms of ordering and concurrency. When would you pick each?

level: middleimportance: must knowfreq 78%

answer

  1. Two axes: concurrency + ordering
  2. flatMap = concurrent, unordered
  3. concatMap = serial, ordered
  4. flatMapSequential = concurrent + ordered (buffered)
  5. default flatMap concurrency 256

basics

~10 s

flatMap: 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 s

All 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
java
// 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

for a junior

Enough to know concatMap keeps order and flatMap doesn't; deeper concurrency nuance is a stretch goal.

for a middle

Core target: articulate the two axes and map all three operators, plus the concurrency parameter and serial cost of concatMap.

for a senior

Adds head-of-line blocking of flatMapSequential, DelayError variants, and reasoning about latency = max vs sum.

for a principal

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

context