Web Performance Calendar

The speed geek's favorite time of year
2024 Edition
ABOUT THE AUTHOR
Photo of Amrik Malhans Amrik Malhans is a web performance specialist, and a full-stack developer at Brookbush Institute. He posts about web performance on his LinkedIn.

The browser makes use of a single main thread for executing most important tasks, it’s responsible for running JavaScript, handling user interactions, and updating the DOM. The main thread also performs the layout and painting.

These are browser’s high-level tasks that are bound to the main thread, so what is this “main thread” we’re talking about? Where does it come from? And why not multiple threads?

What is the “main thread”?

Main thread is a single thread where most of the browser’s work happen, these tasks include:

  • JavaScript Execution – JavaScript is single-threaded by nature, and it’s executed on the main thread by default
  • DOM Manipulation – Updates that take place on the DOM happen on the main thread.
  • Style recalculation – When styles for an element change, they are recalculated and painted on the main thread.
  • User interactions – When user scrolls, clicks on a button, or executes a task on the screen, it all happens on the main thread.

Why a main thread? Why not multiple threads?

When you learn that browser is doing so much work on a single thread, the first thing that comes to mind is why can’t we use multiple threads? We now have much powerful hardware to accommodate for that? I asked the same question and here’s the reason why browser still does most of the main tasks on a single thread.

Although modern browsers now make use of web workers, which are background tasks that do not block the main thread, these can be used for tasks that do not require much user interruption and can be handled in the background. There are other things like SharedArrayBuffer, Atomics and WASM that help solve this problem, but they are beyond the scope of this post, and can be explored on your own.

Single-threaded nature of the JavaScript

JavaScript is the language of the web, and as we all know it is single-threaded by nature, it was designed to be a simple language in the late 90s to avoid complexities like race conditions and deadlocks, which are difficult to work with (especially with a DOM), and can cause a lot of bugs.

The DOM is not thread safe. It’s a tree and has a hierarchical structure, and synchronization tasks in a multi-threaded fashion can lead to severe complexities (multiple locks and mutexes). A single-threaded model makes these things simple and predictable–all the updates happen sequentially which avoids complex inconsistencies.

How does JavaScript handle asynchronous tasks if it’s single-threaded?

JavaScript uses the concept of Event Loop to handle async tasks, and offer a non-blocking behaviour, this allows JavaScript to be responsive in a non-blocking fashion despite being single-threaded.

What is Event Loop? And how does JavaScript work?

In the context of browser, let’s see how things work together under the hood, first we need to understand the Event Loop in JavaScript and how the language manages all the tasks.

If JavaScript is single-threaded you would conclude that the language can only do one thing at once right? But that’s not true! The language is able to handle asynchronous tasks and provide non-blocking I/O operations to run smoothly.

How does that work?

Look at this image below, and let’s analyze how the language processes work.

JS runtime environment
Image taken from MDN

Whenever you a call a function it gets pushed onto a stack, JS uses a FILO (First In, Last Out) data structure for Stack.

  • First In: When a function is called, it gets pushed onto the top of the stack.
  • Last Out: The most recent (last added) function on the stack is executed first. Once it finishes, it is popped off the stack.

Here’s a working visualization to help pain the picture taken from jsv9000:

The Stack handles the execution of functions and Queue in JS is used to manage asynchronous tasks like setTimeout, promises, and I/O operations.

JavaScript uses a task queue and a microtask queue to make these operations possible, and these queues make sure your single-threaded JavaScript is able to run tasks in a non-blocking fashion.

Callback Queue

The callback queue is a FIFO (First In, First Out) data structure, (the first to come in will be the first to go out), this queue handles operations like setTimeout, setInterval, and DOM events.

The event loop moves these tasks from the queue to the call stack when the stack is empty.

Microtask Queue

The microtask queue is also FIFO but has higher priority than the callback queue, this queue handles operations like Promise.then and MutationObserver callbacks.

The microtask queue are executed immediately before moving on to the callback queue, this means tasks in this queue get a higher priority than tasks in callback queue.

And the Event Loop coordinates with these queues and stack to ensure a smooth non-blocking nature we see in JavaScript, to put this all together, here’s how it works step by step:

Execute Synchronous Code:

  • The event loop starts by executing all synchronous code in the call stack.

Check the Microtask Queue:

  • After the call stack is empty, the event loop checks the microtask queue.
  • If there are any tasks in the microtask queue, they are moved to the call stack and executed.

Process the Callback Queue:

  • Once the microtask queue is empty, the event loop processes tasks from the callback queue.
  • These tasks are moved to the call stack and executed one by one.

Repeat:

  • This cycle repeats indefinitely, allowing JavaScript to handle both synchronous and asynchronous code seamlessly.

Here’s an example code to illustrate this:

console.log("Start");

setTimeout(() => {
    console.log("Task 1 (Callback Queue)");
}, 0);

Promise.resolve().then(() => {
    console.log("Task 2 (Microtask Queue)");
});

console.log("End");

When you run this code you will see the output:

Start
End
Task 2 (Microtask Queue)
Task 1 (Callback Queue)

So now you understand how Event Loop works! It’s time to understand how the browser handles tasks using the main thread.

How the Browser Works on the Main Thread

Significant amount of work is done on the main thread, some of it includes:

  • Executing JavaScript: Running synchronous and asynchronous JavaScript code.
  • Handling User Interactions: Processing events like clicks, scrolls, and keypresses.
  • Updating the DOM: Recalculating styles and layout, then painting updates on the screen.
  • Running the Event Loop: Orchestrating task queues, microtasks, and rendering updates.

All these tasks can result in a costly operation, so we need to examine each of them carefully in order to be able to debug them.

Execution of JavaScript

All the synchronous and asynchronous JavaScript code runs on the main thread, and if not managed carefully (long running loops and timeouts), they can block the main thread from operating on other important tasks.

Handling User Interactions

When a user clicks on a button on the page, or performs an action like scroll, all these events are handled on the main thread, the browser must take these inputs, process them and handle the side-effects produced by them or provide the user some feedback.

Updating the DOM

When an element changes due to user input or as a part of some JS code, the browser needs to recalculate the styles, build the CSSOM, layout the new styles and paint it to the screen, all of this is also done on the main thread.

Running the Event Loop

And of course, the checks for queues and running the stack of functions all happens on the main thread, these could be your async operations or synchronous code doing some calculation.

Browser main thread


All these tasks above could spend a significant amount of time and resource on the main thread, and because we’re limited by the capabilities of browser and the language, these tasks must be done carefully, freeing up space for crucial tasks, and not blocking on tasks that needs to execute immediately.

You can think of the main thread as the engine room of a ship, where many essential tasks are carried out (e.g., navigation, maintenance, power generation). The event loop is like the task manager inside that room, ensuring that tasks are handled in a logical and efficient order.

Leave a Reply

You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>
And here's a tool to convert HTML entities