Skip to main content
Back to Blog

How to Generate Large Test Files without Crashing Your Browser

Written by Ankur Prajapati

Published 2026-01-02

LinkedIn profile
Advertisement

Generating a test file in a browser is fast and private: nothing crosses the network. It also has limits that command-line tools do not, because everything happens inside one tab's memory. This post explains how in-browser generation works, where the memory goes, the choices we made for this site, and what to do when you need a file bigger than a browser should make.

How a browser makes a file

Three browser APIs do all the work:

  1. A typed array (Uint8Array) holds the bytes while they are being written.
  2. A Blob wraps those bytes as an immutable file-like object with a MIME type.
  3. An object URL (URL.createObjectURL) gives the Blob a temporary address, which a link with a download attribute can save to disk.

A simplified version of the approach:

function buildFile(template, totalBytes, padByte, mime) {
  if (totalBytes < template.length) throw new Error('size smaller than template');
  const bytes = new Uint8Array(totalBytes);    // zero-filled
  bytes.set(template, 0);                      // valid header and structure
  bytes.fill(padByte, template.length);        // padding up to the exact size
  return new Blob([bytes], { type: mime });
}

function download(blob, name) {
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = name;                           // e.g. "dummy.pdf"
  a.click();
  setTimeout(() => URL.revokeObjectURL(url), 10000);
}

This is simplified in one important way: real formats cannot always be padded at the end. A PDF's cross-reference table stores byte offsets, and a PNG ends with an IEND chunk, so padding has to go into a part of the structure that parsers ignore, and the template has to be written with that in mind. The memory pattern, though, is exactly this.

Where the memory goes

For a file of N bytes, expect to need more than N bytes of memory at the peak:

  • The typed array itself: N bytes.
  • The Blob: depending on the browser, creating it may copy the data into the browser's own storage, briefly doubling usage until the typed array is garbage-collected.
  • The download: the browser reads from the Blob to write the file to disk. Revoking the object URL afterwards lets the Blob be freed; forgetting to revoke it keeps the memory in use until the page is closed.

So a 512 MiB file (536,870,912 bytes) can briefly need around 1 GiB in the tab, on top of the page itself. On a desktop with plenty of free memory, that is fine. On a laptop with many tabs open, or a phone, a much larger allocation can fail outright or cause the browser to kill the tab. Browsers also have per-allocation limits on how big a single typed array can be, and those limits differ between browsers and versions.

Why we cap files at 512 MiB

Our generator builds each file into a single typed array as above, and a single file is capped at 512 MiB. We chose that number because:

  • It covers the limits people test most often: 5, 10, 25, 50 and 100 MB tiers, plus boundary files around each of them.
  • It stays comfortably within what ordinary laptops can allocate in one tab, including the temporary copy.
  • Above that size, command-line tools (below) are simply better: they have no memory ceiling and can write gigabytes in seconds.

Main thread, not a Web Worker

A common recommendation is to move heavy work into a Web Worker so the page stays responsive. This site currently generates on the main thread instead. The work is mostly a template copy and a fill() call, which browsers implement as a tight native loop, so the time is dominated by allocating memory rather than running JavaScript. A worker would also need to transfer the finished buffer back to the page (cheap if the buffer is transferred rather than copied, but more code for little gain at these sizes).

The trade-off is visible at the top end: for files of several hundred MiB, the page can be unresponsive for a moment while the buffer is allocated and filled. That is expected. If a generation ever looks stuck for a long time, the tab is most likely short of memory. Closing other tabs, or choosing a smaller size, usually fixes it.

Tips for large files in the browser

  • Generate one large file at a time. Two 500 MiB files in parallel need twice the memory.
  • Check the result. After download, confirm the exact size (wc -c dummy.pdf, or (Get-Item .\dummy.pdf).Length in PowerShell). A partial download from a crashed tab is smaller than requested.
  • Mind compression. Our text formats are padded with spaces, and repetitive padding compresses very well. If your upload path gzips requests or a proxy compresses traffic, a 100 MB file may travel as far fewer bytes. For realistic transfer tests, use incompressible data from the command line.
  • Mobile devices have much less memory per tab. Keep to tens of MB there.

When you need more than 512 MiB

Use a tool that writes straight to disk. These create files of an exact size without holding them in memory:

# Linux
truncate -s 2G big.bin                 # sparse, instant (2G = 2 GiB)
fallocate -l 2G big.bin                # real blocks, on supporting filesystems
dd if=/dev/urandom of=big.bin bs=1M count=2048   # incompressible, slower

# macOS
mkfile -n 2g big.bin

# Windows
fsutil file createnew big.bin 2147483648

These files are raw bytes with no format, so a server that checks magic bytes will reject them as the wrong type. If you need a large file with a valid structure, stream it from a script: write a small valid template, then padding in chunks, letting the stream apply backpressure so memory stays flat:

import { createWriteStream } from 'node:fs';
import { once } from 'node:events';

const total = 2 * 1024 ** 3;                  // 2 GiB
const chunk = Buffer.alloc(1024 * 1024, 0x20); // 1 MiB of spaces
const out = createWriteStream('big.json');

out.write('{"padding":true}');
let written = 16;
while (written < total) {
  const n = Math.min(chunk.length, total - written);
  if (!out.write(chunk.subarray(0, n))) await once(out, 'drain');
  written += n;
}
out.end();

That produces a valid JSON file of exactly 2,147,483,648 bytes using a few MB of memory, because JSON allows trailing whitespace.

For browser-only workflows that really need multi-gigabyte output, Chromium-based browsers offer the File System Access API (showSaveFilePicker()), which lets a page stream writes to a file on disk instead of building a Blob in memory. It is not available in all browsers.

Summary

  • Browser generation is typed array, then Blob, then object URL; peak memory can be about twice the file size.
  • This site builds files on the main thread and caps them at 512 MiB, which covers common upload limits with room for boundary tests.
  • Above that, or for incompressible data, use truncate, fallocate, dd, mkfile, fsutil or a streaming script.

To pick sizes, the file size calculator converts between MB and MiB, and /upload-limits lists the limits of common services and frameworks. To generate a file now, open the generator.

Advertisement