Prompt and context
A browser must gzip large uploads and decompress downloads with low memory, cancellation, and protection from hostile input. Design the stream pipeline and explain formats, backpressure, errors, and security boundaries.
The Compression Streams API provides CompressionStream and DecompressionStream for binary chunks in a Web Streams pipeline. The standard defines brotli, deflate, deflate-raw, and gzip; it supplies transforms, not size limits, authentication, or business integrity checks.
What the interviewer is testing
Cover Readable/Writable/TransformStream composition, backpressure and queues, flush, format boundaries, decompression errors, cancellation propagation, memory budgets, decompression bombs and length side channels, progressive enhancement, and server negotiation.
30-second answer framework
“I would connect a Blob stream or response body to CompressionStream with pipeThrough so backpressure is preserved, and propagate cancellation with an AbortSignal. On download, cap compressed size, decompressed bytes, and processing time before feeding DecompressionStream to a parser; abort on format or integrity errors. If the browser lacks the capability, fall back to server compression. The API is not a security scanner or integrity verifier.”
Step-by-step deep dive
Step 1: Choose input and output streams
Uploads can start from Blob.stream() and downloads from Response.body. A CompressionStream produces a ReadableStream for a fetch body, file sink, or parser; do not load the whole file into an ArrayBuffer first.
Step 2: Connect a transform
A minimal upload pipeline is:
async function uploadGzip(blob, signal) {
const compressed = blob.stream().pipeThrough(
new CompressionStream("gzip"),
{ signal },
);
return fetch("/upload", {
method: "POST",
body: compressed,
signal,
headers: { "Content-Encoding": "gzip" },
});
}The real protocol must define request length, retry semantics, and whether the server accepts streaming request bodies.
Step 3: Understand backpressure and flush
The Streams API adjusts upstream reads from downstream queue and write speed. Do not append every chunk to an unbounded array; a slow sink should naturally pause the pipeline. Closing the writable side must flush the final compressed block and checksum.
Step 4: Negotiate formats
The constructor accepts a supported format string and throws for an unsupported one. The client and server must agree on Content-Encoding or a business field. deflate, deflate-raw, and gzip have different wrappers; do not send Brotli without negotiation.
Step 5: Make decompression bounded
Compressed input can expand to huge output. Count output bytes, file entries, processing time, and concurrent jobs; cancel or abort once a budget is exceeded. Decompressed content still needs MIME, path, and content-safety checks. A successful transform is not trust.
Step 6: Propagate errors and cancellation
Format or checksum failures put the transform into an errored state. Catch errors from pipeTo, fetch, and readers, and propagate user cancellation to the request, readers, writers, and transform. Release temporary Blobs, locks, and UI progress state so no read remains pending.
Step 7: Protect privacy and integrity
Compressed length can reveal relationships between secrets and attacker-controlled text. Do not put both in one compression context. Use independent signatures or hashes for important files; compression encodes bytes but does not authenticate the source or prevent tampering.
Step 8: Provide compatibility and fallback
Check constructors and formats at startup, and run large jobs in a Worker to protect the main thread. If unsupported, delegate to server compression or upload uncompressed with the same cancellation, size, and error semantics; do not validate only on modern browsers.
Trade-offs and boundaries
Client CPU versus network savings
Compression saves bytes but consumes CPU, battery, and time. On mobile, choose by file type, network quality, and battery; already-compressed formats usually should not be recompressed.
Streaming versus retry simplicity
Streaming keeps memory low but requires chunk-aware server and idempotent retries. For resumable uploads, bind compressed chunks to the multipart protocol; do not retry a consumed ReadableStream as if it were replayable.
Browser versus server decompression
Browser decompression saves server CPU but moves resource and security budgets to the device. For sensitive or highly expanding data, decompress on the server and return a bounded result while the client consumes status and chunks.
Failure drills and evolution plan
The sink becomes slow
Throttle upload speed and inspect queue and memory curves. Confirm chunks are not all retained; if queues still grow, reduce concurrency and let the pipeline pause.
A gzip stream is truncated
Truncate the compressed input and verify failure during flush or checksum validation, a retryable UI state, and release of readers, requests, and temporary objects.
Output exceeds its budget
Use a high-expansion sample and verify that hitting the byte limit aborts immediately instead of materializing or persisting the full output.
Common mistakes and follow-ups
Mistake 1: Assuming the API prevents decompression bombs
Follow-up: What is missing? Application-level byte, time, entry, and concurrency budgets; the API only performs format conversion.
Mistake 2: Treating deflate as gzip
Follow-up: Why not? The wrappers differ, so protocol negotiation and matching formats are required.
Mistake 3: Retrying a consumed stream
Follow-up: What is correct? Rebuild the pipeline from a replayable Blob or chunk source and coordinate an idempotent upload identifier with the server.
Extended follow-ups and model answers
Why does flush matter?
The transform must emit trailer compression data and checksums when input ends. Without closing the writable side, the consumer can receive an incomplete stream.
How do you reduce length side-channel risk?
Separate secrets from attacker-controlled text, avoid a shared compression context, and use protocol padding or a design that does not expose the relationship.
When should compression stay on the server?
Use server-side processing for old browsers, low-battery devices, huge files, or sensitive data. Still cap decompressed output and expose observable progress to the client.