Use Web Workers for Heavy Computation Without Freezing UI

Move CPU-heavy tasks off the main thread with Web Workers and message passing — best practices, fallbacks, and performance considerations.

advancedBrowser and domjavascriptweb workersperformancemultithreading
Published: 11/12/2025
Updated: 11/12/2025

Advertisement

Question

You need to run expensive image-processing computations (filters, resizing) in the browser without freezing the UI. Design a solution using Web Workers. Include error handling, transferable objects, and fallback strategy.


Answer

Web Workers provide a way to run scripts in background threads. They can perform CPU-bound tasks without blocking rendering. For performance-sensitive code (image processing, crypto, large array computation), workers are essential. Use transferable objects (ArrayBuffer) for zero-copy transfers, chunking for large datasets, and structured message protocols to keep code maintainable.

SEO & UX considerations:

  • Offloading work reduces jank -> better Core Web Vitals (CLS, FID).
  • Use fallback to chunked requestIdleCallback for environments without workers.
  • Use worker pools for parallelism when multiple CPU cores are beneficial.

Implementation

Main thread (main.js):

// create worker
const worker = new Worker('/workers/imageWorker.js');

worker.onmessage = (e) => {
  const { type, payload } = e.data;
  if (type === 'result') {
    // payload could be an ImageBitmap or ArrayBuffer
    renderResult(payload);
  } else if (type === 'error') {
    console.error('Worker error:', payload);
  }
};

function processImage(arrayBuffer) {
  // Transfer the buffer to the worker (zero-copy)
  worker.postMessage({ type: 'process', payload: arrayBuffer }, [arrayBuffer]);
}

Worker (workers/imageWorker.js):

self.onmessage = async (e) => {
  try {
    const { type, payload } = e.data;
    if (type === 'process') {
      // payload is an ArrayBuffer
      const result = doHeavyWork(new Uint8ClampedArray(payload));
      // Transfer result back
      self.postMessage({ type: 'result', payload: result.buffer }, [
        result.buffer,
      ]);
    }
  } catch (err) {
    self.postMessage({ type: 'error', payload: err.message });
  }
};

function doHeavyWork(pixels) {
  // example: invert image colors (CPU-heavy for big images)
  for (let i = 0; i < pixels.length; i += 4) {
    pixels[i] = 255 - pixels[i];
    pixels[i + 1] = 255 - pixels[i + 1];
    pixels[i + 2] = 255 - pixels[i + 2];
  }
  return pixels;
}

Fallback (no Web Worker):

// chunked processing on main thread using requestIdleCallback
function processChunked(pixels) {
  let i = 0;
  function work(deadline) {
    while (deadline.timeRemaining() > 0 && i < pixels.length) {
      // process some pixels
      i += 4000;
    }
    if (i < pixels.length) requestIdleCallback(work);
    else renderResult(pixels);
  }
  requestIdleCallback(work);
}

Visualization

sequenceDiagram participant UI participant Main participant Worker UI->>Main: User uploads image Main->>Worker: postMessage(ArrayBuffer) Worker->>Worker: process pixels (no UI lock) Worker-->>Main: postMessage(result ArrayBuffer) Main->>UI: render processed image

Real-World Example & Tips

  • Use createImageBitmap() and OffscreenCanvas (in workers) for efficient canvas operations.
  • For browsers supporting OffscreenCanvas, you can draw directly in a worker.
  • Limit worker creation per CPU core; use a worker pool for many parallel jobs.
  • Use transferable objects for large typed arrays to avoid copy overhead.

Quick Practice

  • Build a worker that computes the sum of a 10M-length Float32Array and returns the result.
  • Measure UI responsiveness (FPS) with and without the worker.

Summary

Web Workers unlock background computation, enabling smooth UIs. Combine transferable objects, worker pools, and graceful fallbacks for production-grade performance.

Related Videos
Watch these videos to learn more about this topic
Frequently Asked Questions

Can Web Workers access the DOM?

No — workers run in a separate global scope and can’t access window/document directly. Use message passing.

Advertisement


Stay Updated

Get the latest frontend challenges, interview questions and tutorials delivered to your inbox.

Advertisement