How to view large CSV files
Excel stops at just over a million rows and slows badly long before that. Here is how to open, search and query a CSV that is far bigger than your spreadsheet can handle.
Where each tool gives up
| Tool | Hard limit | Where it gets painful |
|---|---|---|
| Excel | 1,048,576 rows | ~200k rows with several columns |
| Google Sheets | 10 million cells | ~150k rows × 20 columns, plus a slow import |
| Numbers (macOS) | 1,000,000 rows | ~100k rows |
| Notepad / TextEdit | None stated | ~50 MB, and it is not a table anyway |
| Upload-based web tools | Usually 10k–100k rows | The upload itself |
| Browser viewer | None | Your device's free memory |
| DuckDB / xsv | None | Disk speed — these stream the file |
Opening it in a browser
A well-built browser viewer handles far more than people expect, because it never draws what you cannot see. This one keeps about forty rows in the document at a time and recycles them as you scroll, so the DOM stays the same size whether the file has a thousand rows or a million. Sorting and searching run over arrays already in memory, with no re-read and no network.
In practice a 100 MB CSV opens in a few seconds on an ordinary laptop, and files up to roughly 500 MB are workable. Beyond that you are fighting the browser's per-tab memory ceiling rather than the page.
Close other tabs before opening a very large file. A browser tab's memory budget is shared, and a dozen open tabs is often the difference between a file opening and the tab being killed.
Take a sample first
Most of the time you do not need the whole file — you need to know what the columns are, what the values look like, and whether the export is broken. A few thousand rows answers all three in under a second:
# first 5,000 rows, header included
head -5000 big.csv > sample.csv
# a random sample, header preserved
{ head -1 big.csv; tail -n +2 big.csv | shuf -n 5000; } > sample.csv
# the last rows, to check where an export stopped
{ head -1 big.csv; tail -5000 big.csv; } > sample.csv Splitting a file into openable pieces
# 500,000 rows per piece
split -l 500000 big.csv part_
# with the header repeated on each piece
head -1 big.csv > header.csv
for f in part_*; do cat header.csv "$f" > "with_header_$f"; done
rm header.csv part_* On Windows, with PowerShell:
$reader = [IO.File]::OpenText("big.csv")
$header = $reader.ReadLine(); $i = 0; $n = 0; $writer = $null
while (($line = $reader.ReadLine()) -ne $null) {
if ($i % 500000 -eq 0) {
if ($writer) { $writer.Close() }
$writer = [IO.File]::CreateText("part_$n.csv")
$writer.WriteLine($header); $n++
}
$writer.WriteLine($line); $i++
}
$writer.Close(); $reader.Close() Querying instead of scrolling
Past a certain size, reading a file row by row stops being useful — you want an answer, not a view. DuckDB reads CSV directly with no import step and streams from disk, so file size stops mattering:
# what are the columns?
duckdb -c "DESCRIBE SELECT * FROM 'big.csv'"
# how many rows?
duckdb -c "SELECT count(*) FROM 'big.csv'"
# the rows you actually care about
duckdb -c "SELECT * FROM 'big.csv' WHERE status = 'failed' LIMIT 100"
# write a manageable slice back out, then open it in the viewer
duckdb -c "COPY (SELECT * FROM 'big.csv' WHERE country = 'DE') TO 'de.csv'" That last pattern is the practical workflow for very large files: filter on the command line, view the result in a browser. You get SQL's reach and a real table to read.
Memory rules of thumb
- A CSV loaded into memory as JavaScript strings typically uses 5–10× the file size. A 100 MB file needs roughly 0.5–1 GB free.
- Wide files cost more than tall ones — per-value overhead dominates, so 50 columns × 100k rows is heavier than 5 columns × 1M rows at the same byte count.
- A 64-bit browser tab can usually address about 2–4 GB before it is killed.
- If a tab dies mid-load, the file was too big for the device, not for the viewer. Sample or split it.