HTML Web Workers API

Beginner
⏱️ 14 min read
📚 Updated: Jul 2026
🎯 5 Examples + 3 Try It
Worker / postMessage

Introduction

Web Workers are a powerful browser feature that let scripts run in the background, separate from the main thread that paints the page and handles clicks. That means you can crunch numbers, parse large files, or process data without freezing scroll, animation, or input.

This tutorial explains what workers are, how to create them, how messaging works, and how to avoid common mistakes—with five examples and three interactive Try It demos you can run in your browser.

What You’ll Learn

01

Worker()

Spawn a thread.

02

postMessage

Send data.

03

onmessage

Receive results.

04

onerror

Catch failures.

05

No DOM

Thread limits.

06

Try It

Live demos.

What Are Web Workers?

Web Workers are a way to run JavaScript in background threads. Each worker has its own global scope (accessed as self) and runs independently of the main UI thread. You typically use them for:

  • Long mathematical or statistical calculations
  • Parsing large JSON or CSV files
  • Image, audio, or video processing
  • Sorting or filtering big arrays
  • Prefetching and transforming API data before display

Workers are not separate processes like OS threads in native apps—they are browser-managed threads with a strict security model. They are still one of the best tools for keeping pages responsive in plain JavaScript.

💡
Beginner Tip

Always check typeof Worker !== 'undefined' before calling new Worker(). Very old browsers and some restricted environments may not expose the API.

How Do Web Workers Work?

Workers run in their own global context. They cannot access the DOM, window, document, or most browser APIs tied to the visible page. Communication happens through a messaging channel:

  • Main thread — creates the worker, sends jobs with worker.postMessage(), listens with worker.onmessage.
  • Worker thread — receives jobs in self.onmessage, does the work, replies with self.postMessage().
  • Structured clone — message data is copied between threads (objects, arrays, typed arrays). Functions and DOM nodes cannot be sent.

Because the worker is separate, a heavy loop inside it does not block the main thread—buttons stay clickable and animations keep running.

Creating a Web Worker

The classic pattern uses two files. On the main page, instantiate Worker with the URL of a JavaScript file:

js
const worker = new Worker('worker.js');

In worker.js, write the code that runs in the background thread:

js
self.onmessage = function (event) {
  const result = performTask(event.data);
  postMessage(result);
};

function performTask(data) {
  // Perform computation or processing here
  return data * 2; // Example: double the input
}

Inline worker (single-file demos)

In the Try It editor you often have only one HTML file. Put the worker code in a string and create a Blob—no <style> or extra files needed:

js
var code =
  'self.onmessage = function(e) { postMessage(e.data * 2); };';

var worker = new Worker(
  URL.createObjectURL(new Blob([code], { type: 'application/javascript' }))
);

Our Try It demos use this pattern so everything runs in one HTML page without extra server files.

Communicating with Web Workers

Send data from the main thread with postMessage. Receive replies in onmessage:

js
// main.js
worker.postMessage(5); // Send data to the worker

worker.onmessage = function (event) {
  console.log('Received from worker:', event.data);
};

Inside the worker, mirror the pattern—listen on self.onmessage, reply with postMessage:

js
// worker.js
self.onmessage = function (event) {
  const squared = event.data * event.data;
  postMessage(squared);
};

You can send plain objects: worker.postMessage({ action: 'sum', values: [1, 2, 3] }). The worker receives a deep copy, not a shared reference.

Handling Errors

Workers can throw runtime errors. Catch them on the main thread with worker.onerror:

js
worker.onerror = function (error) {
  console.error('Worker error:', error.message);
  console.error('File:', error.filename, 'Line:', error.lineno);
};

Also call worker.terminate() when you no longer need the worker to free memory. Create a new Worker if you need to run jobs again later.

Common Pitfalls

  • DOM access — Workers cannot touch the DOM. Send results back to the main thread and update the page there.
  • Shared globals — Workers have their own global scope. Variables on window are not visible inside a worker, and vice versa.
  • Large message copiespostMessage clones data. Sending huge objects repeatedly can be slow; consider Transferable objects (like ArrayBuffer) for zero-copy moves.
  • Same-origin worker scripts — The worker script URL must obey the same-origin policy unless CORS allows it.
  • Over-using workers — Spawning a worker for tiny tasks adds overhead. Use workers when work genuinely risks blocking the UI.
  • Browser compatibility — Dedicated Workers are widely supported, but always feature-detect and provide a main-thread fallback for critical paths.

⚡ Quick Reference

TaskCode pattern
Feature detectif (typeof Worker !== 'undefined') { ... }
Create workerconst w = new Worker('worker.js')
Send to workerw.postMessage(data)
Receive from workerw.onmessage = (e) => e.data
Worker listensself.onmessage = (e) => { ... }
Worker repliespostMessage(result)
Stop workerw.terminate()
Create
new Worker(url)

Spawn thread

Send
postMessage()

Both sides

Listen
onmessage

Event handler

No DOM
self only

Worker scope

Examples Gallery

Five short examples from creating a worker to a full page. Each uses plain JavaScript—no styling required to understand the idea. Try It Yourself demos are minimal HTML with no CSS, using inline Blob workers so you do not need a separate worker.js file.

📚 Getting Started

Spawn a worker and exchange your first message.

Example 1 — Create a Web Worker

Check support, then create a worker. In real projects you pass a .js file path. In Try It demos we use an inline Blob (see Example 5).

js
if (typeof Worker === 'undefined') {
  alert('Web Workers not supported');
} else {
  var worker = new Worker('worker.js');
}
Try It Yourself

How It Works

The browser downloads worker.js and starts it on a background thread. The main script continues immediately—worker startup is asynchronous.

Example 2 — Send and Receive Messages

Main thread sends 5. Worker doubles it and sends back 10.

js
var worker = new Worker('worker.js');

worker.postMessage(5);

worker.onmessage = function (event) {
  alert('Worker replied: ' + event.data); // 10
};
js
self.onmessage = function (event) {
  postMessage(event.data * 2);
};
Try It Yourself

How It Works

postMessage schedules work on the worker thread. When the worker calls postMessage back, the main thread’s onmessage fires with the result.

📈 Practical Patterns

Error handling, performance, and a complete page demo.

Example 3 — Handle Worker Errors

Log syntax or runtime errors that occur inside the worker script.

js
const worker = new Worker('worker.js');

worker.onerror = function (error) {
  console.error('Worker error:', error.message);
};

worker.onmessageerror = function () {
  console.error('Could not deserialize message from worker');
};
Try It Yourself

How It Works

Worker errors bubble to the parent as error events. Handle them so users see friendly feedback instead of a silent failure.

Example 4 — Offload a Heavy Loop

Add many numbers in a worker so the page stays clickable. The Try It demo shows a counter that keeps ticking while the worker runs.

js
var worker = new Worker('sum-worker.js');

worker.postMessage(1000000); // add 0+1+2+...+999999

worker.onmessage = function (event) {
  document.getElementById('result').textContent = event.data;
};
js
self.onmessage = function (e) {
  var total = 0, i;
  for (i = 0; i < e.data; i++) {
    total += i;
  }
  postMessage(total);
};
Try It Yourself

How It Works

The same loop on the main thread would freeze animations and input. In a worker, the page keeps responding while the CPU crunches numbers in the background.

Example 5 — Example Usage (Full Page)

One simple HTML file. Click the button, send 10, worker returns 100. No CSS, no extra files—worker code lives inside the page as a string:

html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Web Worker Example</title>
</head>
<body>
  <h1>Web Worker Example</h1>
  <button id="start">Start Worker</button>
  <p id="result"></p>

  <script>
    var workerCode =
      'self.onmessage = function(e) {' +
      '  postMessage(e.data * e.data);' +
      '};';

    var worker = new Worker(
      URL.createObjectURL(new Blob([workerCode], { type: 'application/javascript' }))
    );

    worker.onmessage = function (event) {
      document.getElementById('result').textContent =
        'Result: ' + event.data;
    };

    document.getElementById('start').onclick = function () {
      worker.postMessage(10);
    };
  </script>
</body>
</html>
Try It Yourself

How It Works

The worker string is turned into a Blob URL so everything fits in one file. Click sends 10; the worker multiplies it by itself and posts back 100.

🚀 Common Use Cases

  • Data crunching — aggregate analytics, sort large tables, or run simulations.
  • File parsing — read CSV, JSON, or log files without blocking typing or scrolling.
  • Media processing — resize images, decode audio, or build waveform data.
  • Real-time apps — keep WebSocket message handling smooth while processing payloads.
  • Games and editors — pathfinding, physics ticks, or syntax highlighting in the background.
  • Progressive enhancement — move expensive work off the main thread when Worker is available.

🧠 How Web Workers Work (Step by Step)

1

Feature detect

Confirm typeof Worker before creating a thread.

Guard
2

new Worker(url)

Browser loads the worker script on a background thread.

Spawn
3

postMessage(job)

Main thread sends input data (cloned) to the worker.

Send
4

Worker computes

Heavy logic runs without blocking UI events.

Process
=

onmessage → DOM

Main thread receives the result and updates the page.

📝 Notes

  • Workers are not a replacement for server-side scaling—they help one browser tab stay responsive.
  • Service Workers (for offline caching) are a different API from Web Workers (for background computation).
  • Module workers: new Worker('worker.js', { type: 'module' }) lets worker scripts use import.
  • Terminate idle workers with worker.terminate() to avoid leaking threads.
  • For tiny tasks (< few ms), main-thread code is often faster than worker startup overhead.
  • Try It demos use Blob URLs so you can experiment without uploading separate .js files.

Universal Browser Support

Dedicated Web Workers are supported in all major modern browsers: Chrome, Firefox, Safari, and Edge. Shared Workers and Module Workers have slightly narrower support—check caniuse.com before relying on them in production.

Baseline · Since HTML

Web Workers API

Dedicated Web Workers are supported in all major modern browsers: Chrome, Firefox, Safari, and Edge. Shared Workers and Module Workers have slightly narrower support—check caniuse.com before relying on them in production.

96% Modern browser support
Google Chrome All versions · Desktop & Mobile
Full support
Mozilla Firefox All versions · Desktop & Mobile
Full support
Apple Safari All versions · macOS & iOS
Full support
Microsoft Edge All versions · Chromium & Legacy
Full support
Internet Explorer IE 6+ · Legacy environments
Full support
Opera All modern versions
Full support
Web Workers API Excellent

Bottom line: Safe to use for performance-critical client-side work in 2026. Always feature-detect and offer a main-thread fallback.

💡 Best Practices

✅ Do

  • Use workers for CPU-heavy tasks that risk blocking the UI
  • Keep messages small or use Transferables for large buffers
  • Handle onerror and show user-friendly status text
  • Terminate workers when jobs finish if they are one-shot
  • Design workers around pure data in / data out
  • Feature-detect before calling new Worker()

❌ Don’t

  • Access the DOM from inside a worker
  • Assume globals are shared between threads
  • Spawn dozens of workers for tiny calculations
  • Send functions or DOM nodes through postMessage
  • Forget to clean up Blob URLs after inline workers
  • Block the UI with the same loop you could move to a worker

Conclusion

Web Workers are a great tool for improving web application performance by offloading resource-intensive tasks from the main thread. Create a worker with new Worker(), send jobs through postMessage, and handle results in onmessage while the UI stays smooth.

Understanding worker limitations (no DOM, separate globals) and messaging patterns helps you build responsive dashboards, editors, and offline-capable apps. Try the interactive demos below to see workers in action.

Key Takeaways

Knowledge Unlocked

Five things to remember about Web Workers

Use these points when adding background threads to your apps.

5
Core concepts
💬 02

postMessage

Talk safely.

API
🚫 03

No DOM

Main updates UI.

Limit
04

Performance

Stay smooth.

Why
🛠️ 05

onerror

Handle fails.

Reliability

❓ Frequently Asked Questions

Web Workers let you run JavaScript in a background thread separate from the main page thread. They are ideal for CPU-heavy tasks like parsing large JSON, image processing, or mathematical loops without freezing the UI.
No. Workers cannot read or modify the DOM, window, document, or parent objects. Send data to the main thread with postMessage() and update the page there.
Call new Worker(url) with the path to a .js file, or create an inline worker from a Blob URL for single-file demos. The worker script runs in its own global scope where self refers to the worker context.
Use worker.postMessage(data) from the main thread and self.postMessage(data) from the worker. Listen with worker.onmessage on the main side and self.onmessage inside the worker. Data is copied using the structured clone algorithm.
Dedicated Workers belong to one page (new Worker). Shared Workers can be shared across multiple tabs from the same origin (new SharedWorker). This tutorial focuses on Dedicated Workers—the most common type.
Yes. Dedicated Web Workers are supported in all current major browsers including Chrome, Firefox, Safari, and Edge. Always feature-detect with typeof Worker !== 'undefined' before creating one.
Did you know?

Web Workers inspired later APIs like Service Workers (offline caching) and the Worker constructor for WebAssembly modules. They all run off the main thread, but Web Workers remain the simplest way to parallelize plain JavaScript.

Practice Web Workers in the editor

Run inline Blob workers, send messages, and watch results update—no separate worker file required.

Open Try It editor →

About the author

Mari Selvan M P
Mari Selvan M P 🔗

Developer, cloud engineer, and technical writer

  • Experience 12 years building web and cloud systems
  • Focus Full Stack Development, AWS, and Developer Education

I write practical tutorials so students and working developers can learn by doing—from databases and APIs to deployment on AWS.

5 people found this page helpful