Prefer reading? The full explanation
How Promise Chains Run
Two chains, interleaved · Browser
Before this
How Promises Run — the microtask queue.
After this you can explain
Why two independent .then() chains interleave instead of running one after the other.
Promise.resolve()
.then(() => console.log('A1'))
.then(() => console.log('A2'));
Promise.resolve()
.then(() => console.log('B1'))
.then(() => console.log('B2')); Output: A1 → B1 → A2 → B2
Why do the two chains interleave?
The output is A1 → B1 → A2 → B2. The intuitive answer, A1 → A2 → B1 → B2, assumes that once a chain starts it runs to the end. Promise chains do not work that way.
Running the two statements queues exactly two callbacks: A1 and B1. A2 and B2 do not exist in the queue yet, because a chained .then() attaches to the promise returned by the previous .then(), and that promise has not settled. Registering a handler on an unsettled promise queues nothing.
Each link costs one turn
When A1 runs and returns, the promise it belongs to settles, and only then is A2 queued. At that moment B1 is already in the queue, so A2 lands behind it. B1 then runs and queues B2 behind A2.
The result is that both chains advance exactly one link per turn through the queue. A chain of three .then() calls needs three separate turns, which is why a long chain can be overtaken by shorter work started later.
Chaining versus branching
The experiment below attaches two handlers to the same resolved promise instead of chaining them. Both queue immediately during the synchronous phase, and both run in the first drain, because neither is waiting on the other to settle.
That is the practical distinction. p.then(f).then(g) means "g after f", and costs two turns. p.then(f); p.then(g) means "f and g both when p settles", and costs one.
When the interleaving matters
All four callbacks here run inside a single microtask drain, before any timer or repaint. So interleaving is rarely a performance issue; it is an ordering issue. Code that assumes one chain completes before another begins will read stale state or write in an unexpected order.
If two sequences genuinely must not interleave, express that dependency directly by chaining them together or awaiting one before starting the other, rather than relying on source order.
Reading about it is one thing. Watching it run is another.
↑ Run it step by step