← Back to Blog

Web Workers: Offloading CPU-Intensive Tasks from the Main Thread

May 18, 2026 3 min read By CodeTidy Team

The Main Thread Bottleneck: Why Web Workers Are a Must for Performance-Critical Code

Have you ever noticed how your web application's UI freezes or becomes unresponsive when performing CPU-intensive tasks? This is because the main thread, responsible for handling user interactions and rendering the UI, is blocked by computationally expensive operations. We've all been there - frustrated by the lag, wondering how to offload these tasks without sacrificing performance.

Table of Contents

  • Understanding Web Workers and the Worker API
  • Communicating with Workers using postMessage and Comlink
  • Sharing Data between Workers and the Main Thread
  • Real-World Examples: JSON Parsing, Diffing, and Formatting
  • Best Practices for Using Web Workers
  • Key Takeaways
  • FAQ

Understanding Web Workers and the Worker API

Web workers allow us to run JavaScript code in parallel, freeing up the main thread to focus on rendering the UI and handling user interactions. We can create a new worker using the Worker constructor, passing a script URL or a blob containing the worker code.

// Create a new worker
const worker = new Worker('worker.js');

// Terminate the worker when done
worker.terminate();

The Worker API provides a simple way to communicate with workers using postMessage(). We can send data to the worker, which can then process it and send the result back to the main thread.

Communicating with Workers using postMessage and Comlink

To communicate with a worker, we use the postMessage() method, which sends a message to the worker. The worker can then listen for messages using the onmessage event handler.

// In the main thread
worker.postMessage({ type: 'parse-json', data: jsonData });

// In the worker
self.onmessage = (event) => {
  if (event.data.type === 'parse-json') {
    const parsedData = JSON.parse(event.data.data);
    self.postMessage({ type: 'parsed-json', data: parsedData });
  }
};

For more complex communication scenarios, we can use libraries like Comlink, which provide a higher-level API for working with workers.

// In the main thread
import * as Comlink from 'comlink';

const worker = new Worker('worker.js');
const api = Comlink.wrap(worker);

// Call a method on the worker
const result = await api.parseJson(jsonData);

// In the worker
Comlink.expose({
  parseJson: (data) => JSON.parse(data),
});

Sharing Data between Workers and the Main Thread

When working with large datasets, we need to share data between the worker and the main thread efficiently. One way to do this is by using SharedArrayBuffer, which allows multiple threads to access the same memory region.

// Create a SharedArrayBuffer
const buffer = new SharedArrayBuffer(1024);

// In the main thread
const array = new Int32Array(buffer);
array[0] = 42;

// In the worker
const workerArray = new Int32Array(buffer);
console.log(workerArray[0]); // prints 42

Real-World Examples: JSON Parsing, Diffing, and Formatting

Let's look at some real-world examples of using web workers to offload CPU-intensive tasks.

  • JSON Parsing: When working with large JSON datasets, parsing can be a performance bottleneck. By offloading JSON parsing to a worker, we can keep the main thread responsive.
// In the main thread
worker.postMessage({ type: 'parse-json', data: largeJsonData });

// In the worker
self.onmessage = (event) => {
  if (event.data.type === 'parse-json') {
    const parsedData = JSON.parse(event.data.data);
    self.postMessage({ type: 'parsed-json', data: parsedData });
  }
};
  • Diffing: When comparing large datasets, diffing can be a computationally expensive operation. By using a worker to perform the diff, we can keep the main thread responsive.
// In the main thread
worker.postMessage({ type: 'diff', data: { oldData, newData } });

// In the worker
self.onmessage = (event) => {
  if (event.data.type === 'diff') {
    const diff = diffAlgorithm(event.data.data.oldData, event.data.data.newData);
    self.postMessage({ type: 'diff-result', data: diff });
  }
};
  • Formatting: When formatting large datasets, we can offload the formatting to a worker to keep the main thread responsive.
// In the main thread
worker.postMessage({ type: 'format', data: largeData });

// In the worker
self.onmessage = (event) => {
  if (event.data.type === 'format') {
    const formattedData = formatAlgorithm(event.data.data);
    self.postMessage({ type: 'formatted-data', data: formattedData });
  }
};

Best Practices for Using Web Workers

When using web workers, keep the following best practices in mind:

  • Use workers for CPU-intensive tasks only.
  • Keep worker code modular and reusable.
  • Use postMessage() or Comlink for communication.
  • Avoid shared state between workers and the main thread.

Key Takeaways

  • Web workers allow us to offload CPU-intensive tasks from the main thread.
  • Use postMessage() or Comlink for communication between workers and the main thread.
  • Share data between workers and the main thread using SharedArrayBuffer.
  • Use workers for real-world tasks like JSON parsing, diffing, and formatting.

FAQ

Q: What is the difference between a web worker and a service worker?

A web worker is a dedicated thread for running CPU-intensive tasks, while a service worker is a script that runs in the background, handling network requests and caching.

Q: Can I use web workers with other JavaScript frameworks?

Yes, web workers are a standard JavaScript feature and can be used with any framework or library.

Q: How do I debug web workers?

You can debug web workers using the browser's developer tools, which provide a separate console and debugger for worker code.

AI agent tools available. The CodeTidy MCP Server gives Claude, Cursor, and other AI agents access to 60+ developer tools. One command: npx @codetidy/mcp