Skip to content

Choosing an algorithm

Use case Recommended Why
Web assets (CDN, HTTP) brotli The best compression ratio, and every browser decodes it as a Content-Encoding
General-purpose compression zstd The fastest all-round, with an excellent compression ratio
Real-time data, logs, IPC LZ4 The lowest latency, for speed over ratio
Compatibility with every tool gzip Every platform and tool can decompress it
Many small, similar records (JSON, logs) zstd with a dictionary A trained dictionary seeds the compressor with the patterns to expect
brotli for domain-specific data brotli with a dictionary A custom dictionary of repeated structures, with no training step

In the unified API, the format names follow the Compression Streams standard where it has one: 'gzip', 'deflate' (zlib, RFC 1950) and 'deflate-raw' (RFC 1951), plus 'zstd', 'brotli' and 'lz4'.

Format Range Default
zstd -131072 to 22; negative levels are faster than 1 3
gzip, zlib, raw deflate 0 to 9 6
brotli 0 to 11 (the root functions call it quality) 6
LZ4 none
Call When to use
Sync (compressSync(), zstdCompress()) Small data (under 1 MB), low latency, scripts
Async (compress(), zstdCompressAsync()) Large data, or a server whose event loop must stay free. Browsers run them on the calling thread
Streams (CompressionStream, createZstdCompressStream()) Data of unknown or unbounded size, or bounded memory
Dictionaries (Dictionary, the dictionary option) Many small, structurally similar items
Workers (compress() with workers) Large zstd inputs, compressed on several threads (native addon only)

With the native addon (Node.js, Deno and Bun), the async functions run on the libuv thread pool. The Web streams and Node.js Transforms run each expensive chunk there too, such as 64 KiB at brotli quality 9 or zstd level 19, as node:zlib streams do, so the event loop keeps turning while the chunk is compressed or decompressed. In Node.js, fs, dns, crypto and zlib share that pool, which has 4 threads unless the UV_THREADPOOL_SIZE environment variable sets another number.

Cheap chunks, such as most LZ4 chunks, for which a round trip to the pool would cost more than the work, run on the calling thread, and once such chunks have taken a few milliseconds, the streams wait for the event loop to turn, taking turns when several streams run at once. Each stream predicts the cost of a chunk from its size, the blocks in which the codec compresses its input and the speed of the codec, which it measures as it goes. A single chunk that decompresses to far more than such data usually does, such as 64 KiB of zstd that expands to 16 MiB, can still block the event loop while it is decompressed. This changes when the events of a stream happen, as with node:zlib, not the bytes, the errors or the API.

In browsers, everything runs on the calling thread, async functions included: use a Web Worker to keep a page responsive.