CPU-bound and I/O-bound tasks
A CPU-bound task is limited by the speed of the central processor, while an I/O-bound task is limited by the time spent waiting for an external resource to respond. It is not about how much code you wrote, but about what the bottleneck actually is: computation or waiting. In JavaScript this distinction is critical, because code execution is single-threaded.
Theory
TL;DR
- CPU-bound: the processor limits the speed. Examples: large loops, cryptography, sorting, encryption, compression.
- I/O-bound: input/output (I/O) limits the speed. The processor idles while waiting for a response. Examples: database queries, HTTP, file reads and writes, network calls.
- CPU-bound code uses the processor at 100%, I/O-bound code barely uses it at all.
- JavaScript is single-threaded, so a CPU-bound operation blocks the event loop: the UI does not update, events are not handled, the server does not answer other requests.
- Cure for CPU-bound: Web Workers, Worker Threads, child_process, splitting work into chunks, WebAssembly or native modules.
- Cure for I/O-bound: asynchronous code, caching, connection pools, queues.
Quick example
import fs from 'fs/promises';
// CPU-bound: the thread is busy computing, the event loop is stalled
console.time('cpu');
let total = 0;
for (let i = 0; i < 1e9; i += 1) total += i;
console.timeEnd('cpu');
// I/O-bound: the thread is free while the OS reads the file
console.time('io');
const data = await fs.readFile('large-file.txt', 'utf-8');
console.timeEnd('io');What actually limits the speed
| Task type | What limits the speed | Examples |
|---|---|---|
| CPU-bound | The central processor (CPU), everything comes down to computation | Large loops, cryptography, sorting, encryption, compression |
| I/O-bound | Input/output (I/O), the processor idles while waiting for a response from an external resource | Database queries, HTTP, file reads and writes, network calls |
CPU-bound, tasks limited by the processor
The problem here is long computations that occupy the thread entirely.
// A heavy CPU task: computing a factorial
function factorial(n) {
if (n === 1) return 1;
return n * factorial(n - 1);
}
console.time('CPU');
console.log(factorial(50000)); // it will hang, the stack overflows
console.timeEnd('CPU');Why this is bad in JS (Node.js or the browser): JavaScript is single-threaded, and while a heavy operation runs:
- the UI does not update,
- events are not handled,
- the server does not answer other requests.
Solutions:
- use Web Workers (in the browser) or Worker Threads / child_process (in Node.js);
- split the computation into chunks (
setTimeout,setImmediate); - use WebAssembly or native Rust / C++ modules for heavy calculations.
I/O-bound, tasks limited by input and output
The problem here is not the processor, but the waiting time for an external system to respond.
import fs from 'fs/promises';
console.time('IO');
const data = await fs.readFile('largeFile.txt', 'utf-8'); // waiting on the disk
console.timeEnd('IO');
console.log(data.slice(0, 100));Why this is good for Node.js: Node.js has an event loop and asynchronous I/O, so while the file is being read or the database is answering, the thread is free and can serve other requests.
Comparison and an analogy
| Parameter | CPU-bound | I/O-bound |
|---|---|---|
| Delay caused by | Computation | Waiting for an external resource |
| Uses the CPU | At 100% | Almost not at all |
| Examples | Compression, encryption, image processing, ML | Database queries, APIs, file reads, network operations |
| Fits Node.js | Poorly, unless workers are used | Excellently, thanks to async/await and the event loop |
| How to speed it up | Multithreading, Web Workers, native modules | Asynchronous code, caching, connection pools |
A simple analogy:
- CPU-bound: you are cooking a complex dish yourself and cannot step away, so you are 100% busy.
- I/O-bound: you ordered food delivery and are simply waiting, so you can do other things.
How this affects JS and Node.js
| Scenario | What happens |
|---|---|
| CPU-bound code | Blocks the event loop, every other operation «hangs» |
| I/O-bound code | Node.js efficiently handles tens of thousands of requests in parallel |
Summary:
| Task type | Examples | Optimisation |
|---|---|---|
| CPU-bound | computation, sorting, cryptography, rendering | Workers, native code, splitting into parts |
| I/O-bound | HTTP, databases, files, network | async/await, caching, queues |
Common mistakes
- Believing that
async/awaitspeeds up computation. Asynchrony adds no threads: a heavy loop wrapped inasyncblocks the event loop just the same. - Scaling a CPU-bound service by the number of concurrent requests. While the processor is busy with one computation, the rest of the requests simply queue up.
- Moving trivial work into Worker Threads. Creating a worker and serialising the data costs more than the computation itself.
- Confusing «slow» with «CPU-bound». A slow database query is I/O-bound, and workers will not speed it up: indexes, caching and a connection pool will.
- Splitting computation into chunks with
setTimeout(fn, 0)in Node.js wheresetImmediatefits better, and, in the browser, forgetting aboutrequestIdleCallback.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.