The Depths of Modern Frontend: Main Thread vs Web Workers

The Depths of Modern Frontend: Main Thread vs Web Workers

Every JavaScript developer has probably heard this sentence at least once:
"JavaScript is single-threaded."

But what does that actually mean? And more importantly, how does it affect the performance of our apps — and the experience of real users?

Simply put, everything that runs in the browser (DOM updates, user interactions, API calls, animations, even a simple console.log() ) is processed one by one in a single line. That line is called the Main Thread.

So, when a heavy task comes in, everything else has to wait. That includes clicks, scrolling, or animations. And when users have to wait... they leave. (According to Google, even a small delay can lead to over 50% user drop-off.)

Let’s break this down with a simple analogy.

Imagine a Restaurant...

Picture this: you're in a restaurant. One waiter is doing everything (taking orders, delivering food, clearing tables, wiping them down, even handling the check.)

Now imagine one table orders something complicated maybe a custom birthday cake. The waiter is stuck with that task for a while. Meanwhile, other tables are waiting, getting impatient… some may even walk out.

That waiter is your Main Thread.

And this is exactly what happens in frontend applications. When a big task blocks the Main Thread like processing large data or doing heavy calculations, the UI freezes. Buttons stop responding, animations stutter, and users get frustrated.

So, what's the solution?

Web Workers to the Rescue

Now imagine the restaurant hires a helper — someone working in the back, handling the long tasks while the main waiter continues serving customers up front.

In JavaScript, that helper is called a Web Worker.

A Web Worker runs on a separate thread, meaning it doesn’t block the Main Thread. You can send it data, let it do its job, and then receive the result — all while your UI stays fast and responsive.

Key points:

  • Runs in the background
  • Communicates using postMessage() and onmessage
  • Cannot access the DOM
  • Great for long-running tasks like data processing or math-heavy work

Basic Example: Main Thread vs Web Worker

Here’s a very simple example:

// main.js
const worker = new Worker('worker.js');
worker.postMessage(100000000);

worker.onmessage = function (e) {
  console.log('Result:', e.data);
};
// worker.js
onmessage = function (e) {
  let total = 0;
  for (let i = 0; i < e.data; i++) {
    total += i;
  }
  postMessage(total);
};

This simple loop runs in the background. The UI doesn't freeze, and users can still interact with the page.

Real-Time Comparison: Web Worker vs Main Thread

To make the difference between Main Thread and Web Worker more visible and real, I built a simple demo interface. In this UI, a large JSON file is processed using both the Main Thread and a Web Worker. This lets you directly observe how Web Workers continue working smoothly in the background, while the Main Thread can easily get blocked.

To simulate real-world scenarios like PDF/image processing, WebSocket connections, API polling, or canvas rendering — and also situations where users may have low-performance devices or slow internet — I added a 2-second artificial delay to both sides.

Here’s the basic delay simulation function I used:

function simulateProcessingDelay(duration = 2000) {
  const startTime = Date.now();
  while (Date.now() - startTime < duration) {
    // Busy loop to simulate a 2-second processing delay
  }
}

Main Thread vs Web Worker Demo

Demo and Source Code

The visual comparison below shows how the same task performs on both the Main Thread and the Web Worker. Each side includes the same 2-second artificial delay for fair testing conditions.

Live Demo: https://canbulgay.github.io/web-workers-vs-main-thread/?lang=en

Source Code: https://github.com/canbulgay/web-workers-vs-main-thread

When Should You Use Web Workers?

Web Workers are not needed in every situation. But there are specific cases where offloading work to a Web Worker can significantly improve performance and user experience. Here are some of the most common ones:

Heavy Calculations
Working with large datasets, running loops, doing filtering, sorting, or mathematical operations that consume CPU time.

Single Page Applications (SPA)
SPAs are built with a single HTML file and rely heavily on JavaScript for routing, state management, and UI updates. Since everything already runs on the Main Thread, adding more intensive tasks can easily slow down or freeze the interface.

Data Encryption / Decryption
Running cryptographic algorithms or handling sensitive user data in the browser.

Canvas and Image Processing
Rendering high-resolution images, applying filters, or doing pixel-level operations — like in browser-based games or photo editors. These tasks are CPU-heavy and shouldn't block the UI.

Large File Processing
Parsing or generating PDFs, compressing or resizing images — these tasks take time and benefit from running off the Main Thread.

Network-Intensive Operations
Handling a constant stream of data, like WebSocket messages or API polling, where incoming updates need to be processed before updating the UI.

When Should You Avoid Web Workers?

Even though Web Workers are useful, they aren't always the right choice. Here’s when not to use them:

When You Need Access to the DOM
Web Workers cannot directly interact with the DOM. This means no document, window, alert, console.log, or localStorage.

For Very Simple or Fast Tasks
If a task is lightweight and runs quickly, using a Worker adds unnecessary overhead — creating a Worker, passing messages, and waiting for a response.

Using Too Many Workers
Each Worker runs in its own thread and consumes system memory. Creating too many can slow down performance instead of improving it.
If needed, consider a job queue or implement a thread pool strategy.

Frequent Data Transfers
Communication between the Main Thread and Workers happens through postMessage(). If you're sending large amounts of data back and forth constantly, that communication itself can become a bottleneck.
To reduce this overhead, use Transferable Objects like ArrayBuffer.

Final Thoughts

If your task takes longer than about 100ms, involves continuous data (like from WebSocket streams), or needs to run smoothly on low-powered devices (like mobile phones or older laptops), then using a Web Worker is almost a must.

Especially in apps where UI responsiveness is critical — like Figma, Canva, media players, or collaborative editors — Web Workers aren't just helpful, they're essential.

In short:
When used wisely, Web Workers can improve not only performance but also the overall quality and reliability of your application.