Back

JavaScript Call Stack & Event Loop: How They Work

Doğukan BatalUpdated

On this page
  1. The call stack
  2. Async
  3. Further reading

Every JavaScript developer runs into the call stack and the event loop sooner or later, usually while debugging something async that runs in the wrong order. This post walks through both with two small examples.

The call stack

JavaScript runs on a single thread, so it does one thing at a time with one call stack. Engines like Chrome’s V8 use this stack to track what is running. It works last in, first out (LIFO): the last function added is the first one to finish.

A few other parts work around the stack:

  • Memory heap. Where the variables, objects and functions we create are stored.
  • Web APIs. JavaScript is single-threaded, but the browser is not. When you start something async, like an API request or a timer, the browser handles it outside the stack and the stack moves on. Say you want to log “hello” after one second with setTimeout. setTimeout() goes onto the stack, hands its callback to the Web APIs with the one-second delay, and leaves the stack. Nothing is blocked. After one second the callback moves to the callback queue, and once the stack is empty the event loop pushes it onto the stack.
  • Callback queue. When an async task in the Web APIs finishes, its callback waits here.
  • Event loop. As soon as the call stack is empty, it takes the first callback from the queue and puts it on the stack.

Understanding How the Call Stack and Event Loop Work in JavaScript

In the code below there are three functions. foo logs “foo”, bar logs “bar”, and hello calls foo and bar and then logs “hello”. The last line calls hello().

function foo(){
  console.log("foo");
}
function bar(){
  console.log("bar");
}
function hello(){
  foo();
  bar();
  console.log ("hello");
}
hello();

Animation: hello() calls foo() and bar(), and each one is added to and removed from the call stack

The code runs from top to bottom:

  • hello() goes onto the stack. It calls foo(), so foo is added on top. foo calls console.log, which is added on top of that. Because the stack is LIFO, console.log runs and leaves first, then foo is done and leaves too.
  • hello is still on the stack because it has not finished. Now bar is added, logs “bar” the same way, and leaves.
  • Finally hello logs “hello”. That console.log is added, runs and leaves, and with nothing left to do, hello leaves the stack as well.

Async

Here is the same idea with setTimeout:

console.log("hello");
function world(){
     console.log("world")
}
setTimeout(function cbFunction(){
     console.log("Callback Function")
}, 1000);
world();

Animation: the setTimeout callback moves from the Web APIs to the callback queue, then to the call stack

  • The first line goes onto the stack, logs “hello” and leaves.
  • world is a function that logs “world”. It is called after setTimeout.
  • setTimeout goes onto the stack, passes cbFunction to the Web APIs with its 1000 ms delay, and leaves.
  • world() and its console.log go onto the stack one after the other, run and leave. Meanwhile the 1000 ms pass and cbFunction moves to the callback queue.
  • The event loop sees the stack is empty and pushes cbFunction onto it. It logs “Callback Function” and leaves, and the stack is empty again.

Further reading