One thing at a time
JavaScript runs your code on one thread, with one call stack: a pile of the functions running right now. It does one thing at a time. Waiting is left to the host, the browser or Node.js.
Running the script
The whole script runs as one task, so its frame goes on the stack first. Line 1 calls console.log: a new frame goes on top, prints start, and comes off again.
setTimeout hands the arrow function, a callback, to the host's timer with a delay of 0 ms, and returns at once. JavaScript does not wait. Line 3 does not run now; it is inside the callback.
The timer counts on its own, outside JavaScript. When the 0 ms are up, it puts the callback in the task queue. A queued callback waits there until the call stack is empty.
Promise.resolve() makes a promise that is already fulfilled. .then gives it a callback. The promise is settled, so the callback goes straight into the microtask queue. Line 6 does not run yet either.
Line 8 prints end. It is the last line, so the script's frame comes off and the call stack is empty for the first time. Only now can the waiting callbacks run.
The event loop
The event loop sees an empty stack and checks the queues. Its rule: before the next task, run every waiting microtask. So the promise callback goes before the timeout, even though the timeout was queued first.
The promise callback moves onto the call stack and line 6 runs: it prints promise. The callback ends, its frame comes off, and the microtask queue is now empty.
No microtasks are left, so the loop takes the oldest task: the timeout callback. It goes on the stack and line 3 prints timeout. A delay of 0 ms meant "no sooner than", not "now".
The console reads start, end, promise, timeout. The script always runs to the end first. Then the loop runs every microtask, then one task, and repeats. A slow function blocks all of them.
What happens
// step 1/10 · one-thread
thread one: JavaScript does one thing at a time
call stack one frame per running function call
the newest frame on top, last in, first out
host the browser or Node.js: timers, queues,
and the event loop that feeds the stack
A delay of 0 ms is the shortest wait, not a promise of speed. Browsers make deeply nested timers wait at least 4 ms, and Node.js turns a delay below 1 into 1 ms. Either way the callback waits for an empty stack and for every microtask. The figure shows the timer queuing its callback while the script still runs. The exact moment depends on the host; the printed order does not, because nothing leaves a queue until the script has finished.