Next.js starter your AI actually understands. Ship internal tools in days not weeks. Pre-order $199 $499 → [Get it now]

How to use Web Workers in JavaScript

Web Workers run JavaScript in background threads, keeping the UI responsive during heavy computation. As the creator of CoreUI with over 25 years of JavaScript experience since 2000, I’ve used Web Workers for data processing, image manipulation, and encryption in applications where blocking the main thread caused visible lag. The standard approach creates a worker script, posts messages to it, and receives results via event listeners. This enables true parallel execution in the browser.

Create a worker and communicate with messages.

// worker.js
self.addEventListener('message', (event) => {
  const { data } = event

  const result = heavyCalculation(data)

  self.postMessage(result)
})

function heavyCalculation(numbers) {
  return numbers.reduce((sum, n) => sum + n * n, 0)
}
// main.js
const worker = new Worker('./worker.js')

worker.postMessage([1, 2, 3, 4, 5])

worker.addEventListener('message', (event) => {
  console.log('Result:', event.data)
})

worker.addEventListener('error', (err) => {
  console.error('Worker error:', err)
})

new Worker() creates a background thread from a script file. postMessage sends data to the worker. The worker receives it via the message event on self. Results come back the same way. The main thread never blocks.

Inline Worker with Blob

Create workers without separate files.

const workerCode = `
  self.addEventListener('message', (event) => {
    const numbers = event.data
    const sorted = [...numbers].sort((a, b) => a - b)
    self.postMessage(sorted)
  })
`

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

worker.postMessage([5, 2, 8, 1, 9, 3])
worker.addEventListener('message', (e) => {
  console.log('Sorted:', e.data)
  URL.revokeObjectURL(url)
})

Blob creates a file-like object in memory. createObjectURL generates a temporary URL. This avoids needing a separate worker file. Revoke the URL after the worker is no longer needed.

Transferable Objects for Performance

Transfer large data without copying.

// Slow - copies the buffer
const buffer = new ArrayBuffer(10 * 1024 * 1024)
worker.postMessage(buffer)

// Fast - transfers ownership
const buffer = new ArrayBuffer(10 * 1024 * 1024)
worker.postMessage(buffer, [buffer])
// buffer is now empty in main thread - ownership transferred

By default, data is copied when posted. Transferable objects like ArrayBuffer transfer ownership instantly with zero copy. After transfer, the original reference becomes unusable. Use this for large binary data like images or audio.

Worker Pool Pattern

Reuse multiple workers for throughput.

class WorkerPool {
  constructor(size, script) {
    this.workers = Array.from({ length: size }, () => new Worker(script))
    this.queue = []
    this.available = [...this.workers]
  }

  run(data) {
    return new Promise((resolve, reject) => {
      const task = { data, resolve, reject }
      const worker = this.available.pop()
      if (worker) {
        this.dispatch(worker, task)
      } else {
        this.queue.push(task)
      }
    })
  }

  dispatch(worker, task) {
    worker.onmessage = ({ data }) => {
      task.resolve(data)
      const next = this.queue.shift()
      if (next) {
        this.dispatch(worker, next)
      } else {
        this.available.push(worker)
      }
    }
    worker.onerror = (err) => task.reject(err)
    worker.postMessage(task.data)
  }
}

const pool = new WorkerPool(4, './worker.js')
const results = await Promise.all(
  items.map(item => pool.run(item))
)

The pool maintains a set of workers and a task queue. When a worker finishes, it picks the next task. This maximizes CPU utilization across cores.

Best Practice Note

This is the same Web Worker pattern we use in CoreUI for data transformation and heavy chart calculations without blocking the UI. Use workers only for genuinely CPU-intensive work - the communication overhead isn’t worth it for fast operations. Workers can’t access the DOM, window, or document. They can use fetch, IndexedDB, and crypto. For React or Vue apps, consider dedicated libraries like comlink which simplify worker communication with a Promise-based API. Always terminate workers when done to release memory.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author

Subscribe to our newsletter
Get early information about new products, product updates and blog posts.
How to change opacity on hover in CSS
How to change opacity on hover in CSS

Understanding the difference between `for...in` and `for...of` statements in JavaScript
Understanding the difference between `for...in` and `for...of` statements in JavaScript

JavaScript printf equivalent
JavaScript printf equivalent

Understanding the Difference Between NPX and NPM
Understanding the Difference Between NPX and NPM

Answers by CoreUI Core Team