Skip to content
Laviyx Learn

Try

JavaScript course menu

Asynchronous JavaScript and the Event Loop Explained

Understand asynchronous JavaScript: why JavaScript is single-threaded, blocking versus non-blocking code, setTimeout and setInterval, the call stack, task queue, microtasks and the event loop, and the order in which async code runs.

Intermediate7 min readUpdated
On this page

Some work takes time: downloading data, waiting for a timer, reading a file. If JavaScript simply stopped and waited, the whole page would freeze. Instead, it starts the slow work, carries on with other code, and handles the result later. This is asynchronous programming.

One thing at a time#

JavaScript runs your code on a single thread: it executes one statement at a time, and a long-running loop blocks everything else, including clicks and screen updates in a browser.

JavaScript
// Do not do this in a web page: the tab freezes for 5 seconds
const end = Date.now() + 5000;
while (Date.now() < end) {}

The browser (or Node.js) itself, however, can do many things in the background: run timers, download files, wait for user input. When one of those finishes, it queues a callback for JavaScript to run when it is free.

setTimeout#

setTimeout(callback, delay) runs a function once, after at least delay milliseconds:

JavaScript
console.log("Order placed");

setTimeout(() => {
  console.log("Order shipped (after 100 ms)");
}, 100);

console.log("Thank you for shopping!");
Output
Order placed
Thank you for shopping!
Order shipped (after 100 ms)

The script does not wait for the timer: it prints the thank-you line first. setTimeout returns an id that clearTimeout(id) can use to cancel it.

setInterval#

setInterval(callback, delay) repeats until cancelled with clearInterval:

JavaScript
let count = 3;
const id = setInterval(() => {
  console.log(count);
  count--;
  if (count === 0) {
    clearInterval(id);
    console.log("Lift off!");
  }
}, 50);
Output
3
2
1
Lift off!
HTML
<p id="clock" style="font: 600 28px monospace">0.0 s</p>
<button id="stop">Stop</button>
JavaScript
const start = Date.now();
const timer = setInterval(() => {
  document.querySelector("#clock").textContent = `${((Date.now() - start) / 1000).toFixed(1)} s`;
}, 100);
document.querySelector("#stop").addEventListener("click", () => clearInterval(timer));
Result

The event loop#

Three parts work together:

  1. The call stack: the function JavaScript is running right now.
  2. The task queue (also called the macrotask or callback queue): callbacks waiting to run, from timers, events and I/O.
  3. The event loop: whenever the call stack is empty, it takes the next callback from the queue and runs it.

So a timer callback can only run when all the current code has finished. A delay of 0 does not mean "now"; it means "as soon as you are free":

JavaScript
console.log("A");
setTimeout(() => console.log("B (timeout 0)"), 0);
console.log("C");
Output
A
C
B (timeout 0)

The delay is a minimum, not a guarantee: if the main thread is busy, timers run late.

Microtasks run first#

Promises (next lesson) use a separate, higher-priority microtask queue. After each task, the event loop runs all pending microtasks before taking the next task:

JavaScript
console.log("1. script start");

setTimeout(() => console.log("5. timeout (task)"), 0);

Promise.resolve()
  .then(() => console.log("3. promise (microtask)"))
  .then(() => console.log("4. second promise (microtask)"));

console.log("2. script end");
Output
1. script start
2. script end
3. promise (microtask)
4. second promise (microtask)
5. timeout (task)

This exact ordering question is common in interviews. The rule: synchronous code first, then all microtasks (promises), then the next task (timers, events).

Where asynchronous code appears#

SourceExample
TimerssetTimeout, setInterval
User eventsClicks, key presses, form submissions
Networkfetch requests to APIs
Files and databases (Node.js)Reading files, querying databases
AnimationrequestAnimationFrame (runs before the next screen repaint)

Early JavaScript handled all of these with callbacks, which led to deeply nested code (Callbacks). Promises and async/await are the modern way to write it.

Practice#

Practice exercise

Predict the order of the output, then run it:

JavaScript
console.log("start");
setTimeout(() => console.log("timeout 1"), 0);
Promise.resolve().then(() => console.log("promise"));
setTimeout(() => console.log("timeout 2"), 0);
console.log("end");
Show solution
JavaScript
console.log("start");
setTimeout(() => console.log("timeout 1"), 0);
Promise.resolve().then(() => console.log("promise"));
setTimeout(() => console.log("timeout 2"), 0);
console.log("end");
Output
start
end
promise
timeout 1
timeout 2

Synchronous logs first, then the promise callback (a microtask), then the two timers in the order they were scheduled.

Key takeaway#

Finished this lesson?