Halloween DealsAmazon USHalloween week deals: useful picks before month-endAmazon US: current deals, useful picks and tech finds.Check DealsPC HealthRecommendedCrashes, freezes, slowdowns? Check your PC nowSpot repairable issues before they interrupt work.Check PCHalloween DealsAmazon USOctober picks are moving - compare before checkoutAmazon US: current deals, useful picks and tech finds.See Picks×
FREEDOM251

Freedom251 › Blog › event loop

JavaScript Event Loop: A Clear Guide to Tasks, Microtasks, and Timing

By Freedom251 Team · · 8 min read

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A useful, deliberately simplified browser model is:

  1. Run one task, such as the initial script, a click handler, or a timer callback.
  2. When that task finishes, drain the microtask queue.
  3. Give the browser an opportunity to update rendering, when appropriate.
  4. 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:

  1. A
  2. B
  3. promise
  4. timer

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Where 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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.

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.Support on Ko-Fi

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

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

  1. Mark the current synchronous job. Write down every statement that executes before the stack returns.
  2. List callbacks as they are registered. Separate promise reactions and explicit microtasks from timers, events, and other tasks.
  3. Drain microtasks at the checkpoint. Include microtasks added by other microtasks; they also run before the next task.
  4. 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.
  5. 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

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.

event loopJavaScriptmicrotasksNode.jsPromisesWeb development

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Recommended PC Tool
Recommended PC Tool
Crashes, No Sound, or Screen Glitches?Free driver scan
Windows Errors? Fix Them Before They SpreadFree repair scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.