The Event Loop is a source of confusion for many developers, but it's a fundamental piece of the JavaScript engine. It's what allows JavaScript to be single-threaded, yet able to execute in a non-blocking fashion. To understand the Event Loop, we first need to explain a few things about the JavaScript engine, such as the Call Stack, Tasks, Microtasks and their respective Queues. Let's break them down one by one.
The Call Stack is a data structure that keeps track of the execution of JavaScript code. As the name suggests, it's a stack, thus a LIFO (Last In, First Out) data structure in memory. Each function that's executed is represented as a frame in the Call Stack and placed on top of the previous function.
Let's look at a simple example, step by step:
function foo() {
console.log('foo');
bar();
}
function bar() {
console.log('bar');
}
foo()
is pushed onto the Call Stack.foo()
is executed and popped off the Call Stack.console.log('foo')
is pushed onto the Call Stack.console.log('foo')
is executed and popped off the Call Stack.bar()
is pushed onto the Call Stack.bar()
is executed and popped off the Call Stack.console.log('bar')
is pushed onto the Call Stack.console.log('bar')
is executed and popped off the Call Stack.Tasks are scheduled, synchronous blocks of code. While executing, they have exclusive access to the Call Stack and can also enqueue other tasks. Between Tasks, the browser can perform rendering updates. Tasks are stored in the Task Queue, waiting to be executed by their associated functions. The Task Queue, in turn, is a FIFO (First In, First Out) data structure. Examples of Tasks include the callback function of an event listener associated with an event and the callback of setTimeout()
.
Microtasks are similar to Tasks in that they're scheduled, synchronous blocks of code with exclusive access to the Call Stack while executing. Additionally, they are stored in their own FIFO (First In, First Out) data structure, the Microtask Queue. Microtasks differ from Tasks, however, in that the Microtask Queue must be emptied out after a Task completes and before re-rendering. Examples of Microtasks include Promise
callbacks and MutationObserver
callbacks.
Microtasks and the Microtask Queue are also referred to as Jobs and the Job Queue.
Finally, the Event Loop is a loop that keeps running and checks if the Call Stack is empty. It processes Tasks and Microtasks, by placing them in the Call Stack one at a time and also controls the rendering process. It's made up of four key steps:
To better understand the Event Loop, let's look at a practical example, incorporating all of the above concepts:
console.log('Script start');
setTimeout(() => console.log('setTimeout()'), 0);
Promise.resolve()
.then(() => console.log('Promise.then() #1'))
.then(() => console.log('Promise.then() #2'));
console.log('Script end');
// LOGS:
// Script start
// Script end
// Promise.then() #1
// Promise.then() #2
// setTimeout()
Does the output look like what you expected? Let's break down what's happening, step by step:
console.log()
is pushed to the Call Stack and executed, logging 'Script start'
.setTimeout()
is pushed to the Call Stack and executed. This creates a new Task for its callback function in the Task Queue.Promise.prototype.resolve()
is pushed to the Call Stack and executed, calling in turn Promise.prototype.then()
.Promise.prototype.then()
is pushed to the Call Stack and executed. This creates a new Microtask for its callback function in the Microtask Queue.console.log()
is pushed to the Call Stack and executed, logging 'Script end'
.Promise.prototype.then()
that was queued in step 5.console.log()
is pushed to the Call Stack and executed, logging 'Promise.then() #1'
.Promise.prototype.then()
is pushed to the Call Stack and executed. This creates a new entry for its callback function in the Microtask Queue.Promise.prototype.then()
that was queued in step 10.console.log()
is pushed to the Call Stack and executed, logging 'Promise.then() #2'
.setTimeout()
that was queued in step 3.console.log()
is pushed to the Call Stack and executed, logging 'setTimeout()'
.setTimeout()
indicates a minimum time until execution, not a guaranteed time. This is due to the fact that Tasks execute in order and that Microtasks may be executed in-between.Snippet collection
Prepare for your next JavaScript interview with this collection of interview questions and answers.
JavaScript, Browser
Adds multiple event listeners with the same handler to an element.
JavaScript, Browser
Copies a string to the clipboard.
Only works as a result of user action (i.e. inside a click
event listener).
JavaScript, Browser
Creates a pub/sub (publish–subscribe) event hub with emit
, on
, and off
methods.