The JavaScript event loop is the scheduling system that lets one JavaScript execution thread handle work that finishes later. Your synchronous code runs to completion; the browser or Node.js host then chooses when queued callbacks can run. Promise reactions usually run as microtasks before the next timer or input task, while a setTimeout(..., 0) callback is only eligible for a later turn—not immediate.
What the event loop actually does
JavaScript itself executes instructions on a call stack. A function is called, its statements run, and its stack frame returns. The runtime does not normally pause that function halfway through to run an unrelated callback on the same agent. This run-to-completion rule makes ordinary synchronous code predictable: once a turn starts, the current job must finish before another queued job can take over. MDN describes this execution model, including agents, execution contexts, and queues, in its JavaScript execution model.
As an Amazon Associate I earn from qualifying purchases.
The event loop is the repeated process around that stack. A host environment—such as a browser or Node.js—provides timers, network and file-system APIs, user-input handling, and queues. When one of those operations has work ready, the host schedules a callback. The JavaScript engine runs that callback only when the current stack is empty and the host selects the callback according to its scheduling rules.
A useful, deliberately simplified browser model is:
#1 Best Overall
- Run one task, such as the initial script, a click handler, or a timer callback.
- When that task finishes, drain the microtask queue.
- Give the browser an opportunity to update rendering, when appropriate.
- Choose another task and repeat.
That sequence is a teaching model, not a promise that a screen repaint follows every task. Browsers have multiple task sources and may combine, delay, or skip rendering opportunities. The platform-level details are specified in the WHATWG HTML Standard, while MDN’s in-depth microtask guide explains how agents, event loops, tasks, microtasks, and rendering fit together.
A small example: why the output is A, B, promise, timer
console.log("A");
setTimeout(() => console.log("timer"), 0);
Promise.resolve().then(() => console.log("promise"));
console.log("B");
In a browser-oriented teaching example, the expected order is:
ABpromisetimer
The initial script is the current task. It prints A, registers a timer, schedules a promise reaction, and prints B before the script can finish. The then() reaction is a microtask. Once the script’s stack is clear, the browser drains pending microtasks before selecting the next task. The timer callback is a task, so it waits for a later turn. This is a standards-based illustration of browser scheduling, not a claim that every asynchronous API uses one universal queue.
Free tools Windows power users keep installed
One-click scans. No signup required.
Tasks and microtasks are different queues
| Feature | Task | Microtask |
|---|---|---|
| Typical sources | Timer callbacks, user events, an initial script, and many host-scheduled operations | Promise reactions, queueMicrotask(), and MutationObserver callbacks |
| When it runs | On a later event-loop turn after the host selects that task | After the current JavaScript stack clears, before the event loop moves to its next task |
| Ordering | Competes with other task sources under host scheduling rules | All currently queued microtasks are drained before the next task is selected |
| Main risk | Can be delayed by long-running JavaScript or other runnable work | Can starve timers, input, and rendering if microtasks keep adding more microtasks |
Promise fulfillment and rejection handlers are not run inline when you call then(). They are queued as microtasks. You can enqueue one explicitly with queueMicrotask(() => { ... }). MDN’s guide to using microtasks also covers the relationship between promise reactions, queueMicrotask(), and mutation observers.
Rank #2
Microtasks are useful for consistent “finish this turn, then do a small follow-up” behavior. They are not a general-purpose background thread. A long microtask still blocks the main thread, and a chain that continually schedules another microtask can keep the queue from emptying. MDN identifies this starvation pattern: later tasks and a browser’s rendering work may be postponed until the microtask queue finally becomes empty.
Why setTimeout(..., 0) is not immediate
A zero-delay timer requests that its callback become eligible after the timer’s minimum-delay rules. It does not interrupt the current function, jump ahead of already queued microtasks, or guarantee execution at exactly zero milliseconds. The timer callback is placed into a task queue, and current JavaScript, other runnable tasks, host throttling, and scheduling policy can all add delay. The HTML Standard’s timer section defines the browser timer rules.
Use a zero-delay timer when you intentionally want work to occur in a future task—for example, to let the current call stack return before processing a larger batch. Do not use it as a precise sleep, a frame-rate clock, or a guarantee that the callback runs before user input or another task.
Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Repair Windows errors before they cause bigger problems3Scan for outdated or missing drivers - takes under a minuteWhere browser APIs fit
The JavaScript engine is not the whole runtime
The engine evaluates JavaScript and manages the call stack, heap, and jobs. The browser surrounds it with host APIs: timers, network requests, DOM events, storage, media, and rendering. Those APIs can perform work outside the JavaScript stack and later schedule callbacks back onto an agent’s event loop. Saying “JavaScript is single-threaded” therefore describes execution on one agent, not every operation performed by the browser.
The main window and workers use agents
In the common window case, page JavaScript and much of the user-interface work share a main thread. A long synchronous loop can therefore make scrolling, clicks, and painting feel frozen. Web workers have their own agent and event-loop context; they are not merely another queue in the window’s loop. Messages between a worker and a window are delivered asynchronously, so a worker can move computation away from the window’s main execution agent without sharing its call stack.
Rendering is an opportunity, not a queue you control
After a task and its microtasks, the browser may update styles, layout, and pixels before choosing more work. It can also decide that no rendering is needed yet. Code should treat painting as host-controlled. If you need to coordinate visual updates, use browser rendering primitives designed for that purpose rather than assuming that every timer or promise corresponds to one frame.
Node.js has the same idea but a different loop
Node.js uses an event loop to provide non-blocking I/O while JavaScript runs on a single thread by default. Its runtime and the operating system can handle network, file, and other operations, then make callbacks runnable. Node’s documented loop is organized into phases, including timers, poll, and check; the exact behavior is not the browser’s “one task, then paint” model. See the official Node.js event-loop guide for current phase and timer details.
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minute| Aspect | Browser | Node.js |
|---|---|---|
| Host work | DOM events, timers, network, rendering, and other web APIs | Network and file-system I/O plus timers and process-oriented APIs |
| Scheduling description | Task sources, a microtask checkpoint, and possible rendering opportunities | Phases such as timers, poll, and check, with Node-specific queues and rules |
| Visual work | The host may render between turns | No browser page-paint step in the server runtime |
| Portable assumption | Promise reactions are microtasks; timer callbacks are later work | Promises are asynchronous, but phase and priority details require Node documentation |
process.nextTick() and setImmediate() are Node-specific
Node exposes scheduling APIs that do not map one-for-one to browser APIs. process.nextTick() uses a Node-managed queue that is processed with special priority, while setImmediate() is associated with Node’s check phase. Their relative behavior can depend on where they are scheduled, such as from the main module or an I/O callback. If code relies on that ordering, consult the current Node guide instead of applying a browser diagram.
Rank #4
Run-to-completion explains freezing and responsiveness
Consider this loop:
const started = Date.now();
while (Date.now() - started < 5000) {
// five seconds of synchronous work
}
While this task occupies the stack, the same agent cannot run a click handler, promise reaction, or timer callback. Those callbacks may be ready, but readiness is not interruption. The visible result is a frozen interface in a browser or delayed I/O callbacks in a server process. Splitting work into smaller tasks, yielding between chunks, or moving suitable computation to a worker can restore responsiveness; adding more promises inside the same long task does not.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Common event-loop mistakes and their fixes
“Async” means it runs in parallel
Asynchronous APIs arrange for later notification; they do not automatically execute JavaScript callbacks simultaneously on the same agent. Use workers or another supported parallelism mechanism when computation truly needs a separate execution context.
“Zero milliseconds” means a deadline
A timer delay is an eligibility condition, not a delivery timestamp. Measure elapsed time when timing matters, and design for delays caused by current work and host scheduling.
Promises always beat everything
Promise reactions normally beat a subsequently selected timer task because microtasks are drained first. That does not make promises universally first: the ordering depends on when each callback is queued, which host is involved, and whether Node-specific queues or phases participate.
Best Value
Microtasks are harmlessly tiny
Each microtask may be short, yet an unbounded chain can monopolize the checkpoint. Bound recursive scheduling, occasionally yield to a task, and avoid doing large batches in one microtask when input and rendering must remain responsive.
There is one global browser queue
Browsers schedule work for agents and have multiple task sources. A window and a worker do not share one global JavaScript stack. Keep the agent and host in mind when explaining ordering.
A practical way to predict callback order
- Mark the current synchronous job. Write down every statement that executes before the stack returns.
- List callbacks as they are registered. Separate promise reactions and explicit microtasks from timers, events, and other tasks.
- Drain microtasks at the checkpoint. Include microtasks added by other microtasks; they also run before the next task.
- Apply the host’s rules. For browser code, account for task sources and possible rendering. For Node code, identify relevant phases and Node-specific queues.
- Check for blocking work. A long callback delays everything behind it, regardless of whether those callbacks are timers, promises, or input handlers.
This method predicts the common examples without pretending that all hosts share one universal queue. When behavior is surprising, reduce the code to one synchronous log, one microtask, and one host callback, then consult the documentation for the environment that actually runs it.
Further reading
For current platform behavior, start with the MDN execution-model and microtask pages, the WHATWG HTML Standard, and Node’s event-loop guide linked above. For a deeper but older book treatment, O’Reilly lists Kyle Simpson’s You Don’t Know JS: Async & Performance as a 296-page, intermediate-to-advanced title published in February 2015. Its contents include “Asynchrony: Now & Later,” the event loop, promises, generators, and Web Workers. The publisher listing is available at O’Reilly; use current platform documentation for details that may have evolved since publication.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

