When nested route segments each declare a server-side data fetch, what decides whether they run in parallel or become a waterfall?
answer
- matching reveals every segment up front
- independent reads overlap, chained reads add
- the child waiting on the parent's return value
- derive the input from the URL, not the parent
basics
~20 sIndependently declared segment fetches can all start the moment the URL is matched, so they overlap. A waterfall appears when a child's fetch needs a value the parent's fetch produced, forcing it to wait for the parent to resolve first.
solid answer
~40 sMatching resolves the whole chain of segments up front, so the framework knows every segment's data function before rendering and can start them together. Their latencies then overlap and the route costs roughly the slowest fetch. A waterfall appears when that independence is broken — most often because a child derives its input from the parent's result, so it cannot start until the parent finishes and the costs add up instead. The usual fix is to remove the dependency rather than to speed the fetches up: derive the child's input from the URL, which both segments already have, or move the dependent read up into the segment that produced the value. Sequential `await` calls inside one data function create the same shape at a smaller scale, so look there too.
go deeper
Remember the two shapes and their cost: reads that start together cost about the slowest one, reads that wait on each other cost the sum. Nesting alone does not decide which you get.
Explain why matching before rendering is what makes overlap possible, name the dependency that breaks it, and show the fix of deriving a child's input from the URL instead of the parent's result.
Demonstrate measurement: compare total data-phase time with the slowest read, instrument per-read timestamps, and argue which chained reads are genuinely irreducible versus accidental.
Weigh a blanket parallel-by-default rule against deliberate gating for authorisation, and decide what the team's route budget is and how a violation gets caught before it ships.
## Why parallel is possible at all Routing happens before fetching. When a URL matches a chain such as an outer section, a middle collection and a leaf detail view, the framework knows *all* the matched modules — and therefore all their data functions — before it renders anything. It can invoke them together and wait for all of them. That is the whole argument for declaring data on the route instead of discovering it while rendering: a render discovers needs one component at a time, but a match reveals them all at once. With **n** independent fetches started together, the route's data phase costs roughly `max(t1..tn)`. Run sequentially, it costs `t1 + ... + tn`. Three 200 ms reads are 200 ms or 600 ms depending on nothing but whether they were allowed to overlap. ## What creates a waterfall A waterfall is a serialised chain. The causes, in the order they show up in real code: 1. **A data dependency between segments.** The child's fetch needs an identifier or a field that only the parent's fetch returns, so it cannot be issued until the parent resolves. 2. **Sequential awaits inside one data function.** `const a = await one(); const b = await two(a?)` — if `two` does not actually need `a`, the two reads are independent and should be started together and awaited together. 3. **Fetching by a value that has to be looked up first.** Reading a record by a slug, then reading its children by the returned id, is two round trips where one query could return both. 4. **A guard that must resolve before anything else may start.** Some checks genuinely must precede the reads; the honest question is whether *all* the reads need to wait or only the privileged ones. | Shape | Cost of the data phase | Typical cause | Typical fix | |---|---|---|---| | Parallel | About the slowest fetch | Segments declare independent reads | Keep it that way | | Parent-then-child | Sum of both | Child needs the parent's returned value | Derive the child's input from the URL | | Serial inside one function | Sum of all awaits | Awaiting each read as it is written | Start all, await all together | ## Removing the dependency The most effective fix is usually structural rather than performance tuning. - **Use the URL as the shared input.** Every segment sees the matched path segments and the query string. If both the parent and the child can derive what they need from the URL, neither has to wait for the other. - **Move the dependent read to where the value is produced.** If the child genuinely needs a parent-derived id, having the parent fetch both and hand the result down turns two sequential round trips into one. - **Ask the data source for the shape you render.** One query that returns a record and its related rows beats two that discover each other. - **Start, then await.** Inside a single data function, issue the independent reads first and await them together, so the writing order does not become the execution order. When a dependency is real and irreducible, accept it and budget for it: the route's first-byte time is the sum, and the decision then becomes whether the dependent part needs to block the document at all — a separate mechanism owns deferring it. ## Where frameworks differ Meta-frameworks are not identical here. Some start every matched segment's data function concurrently as a matter of course; others give the parent a way to gate or short-circuit the chain, which reintroduces ordering deliberately — for example so a failed check in an outer segment prevents an inner read from ever running. Both designs exist and both are defensible; what does not vary is the arithmetic. Overlapping reads cost the maximum, chained reads cost the sum. ## How to spot it A waterfall is visible without any special tooling: measure the time the data phase takes and compare it with the slowest individual read. If the total is close to the sum of the parts, the reads are chained. Server-side timing around each fetch, or a trace showing start and end timestamps per read, turns that from a suspicion into a diagram — and the diagram is what you show in an interview when asked how you found it.
- A route's data phase takes 900 ms while no single read exceeds 300 ms. What does that tell you?That the reads are chained rather than overlapping: the total is the sum, not the maximum. Instrument each read's start and end timestamps, find which one starts only after another resolves, and check whether its input really comes from that result or could be derived from the URL.
- Does declaring data on the route guarantee no waterfalls?No. It removes the render-order waterfall, because matching exposes every segment's requirement before rendering. It cannot remove a genuine data dependency, and it does nothing about awaits written sequentially inside one data function — that shape is just a waterfall at a smaller scale.
- When is a deliberate parent-then-child ordering the right design?When the parent's work decides whether the child's should happen at all — an authorisation or existence check whose failure makes the inner read pointless or unsafe. You are buying correctness with latency. Keep the gate as cheap as possible and let everything not behind it run in parallel.
saying these in an interview costs you the question
- Thinks nesting depth by itself forces sequential fetching
- Believes the framework merges segment fetches into one request
- Assumes parallel means the framework will fix a real data dependency
- Writes sequential awaits and calls the result parallel
- Treats a slow route as a network problem rather than a shape problem