BED File Size Calculator: Estimate Storage and Memory Requirements
By Dr. Zubair Khalid, DVM, MS, PhD ·

A BED file is small until it is not. Ten thousand peaks are a rounding error on any laptop. Twelve million regulatory elements from a whole-genome segmentation run are a different problem, and the difference between a 90 MB file and a 400 MB working set decides whether a job fits on a shared node or dies at hour six with an out-of-memory kill.
By the end of this guide you will be able to predict the byte size of a BED file from its record count and column layout, measure the compression ratio your own data will achieve, budget the index files and temporary space that people forget, and estimate peak RAM for the tools that read BED files. You will also know where the estimate breaks and how to replace it with a measurement in under a minute.
To follow the walkthrough you need a POSIX shell (bash or zsh, Linux or macOS), core utilities including awk, wc, sort, and gzip, and ideally bgzip and tabix from HTSlib plus bedtools. You need at least one representative BED file, even a truncated one. Nothing here requires a cluster, though the RAM commands assume Linux /usr/bin/time.
What a BED File Actually Is
BED stands for Browser Extensible Data. The format was defined for the UCSC Genome Browser as a plain-text, tab-delimited way to describe genomic intervals on a reference assembly. Each line is one interval. There is no header block, no magic number, and no compression by default. That simplicity is why BED survived two decades of tool churn, and also why files from different pipelines rarely look alike.
The first three columns are mandatory and define a minimal BED3 record:
chrom, the sequence name, which must match the reference exactly, including thechrprefix conventionchromStart, the 0-based position of the first base in the intervalchromEnd, the position of the base after the last base, so the interval is half-open
BED is 0-based and half-open. A record chr1 1000 2000 covers bases 1001 through 2000 in 1-based coordinates. GFF3 and GTF are 1-based and closed, and VCF is 1-based. Mixing conventions is the single most common source of silent coordinate errors in interval work.
Optional columns extend the format:
| Column | Name | Typical content |
|---|---|---|
| 4 | name | peak identifier, gene name, or variant ID |
| 5 | score | integer 0 to 1000 |
| 6 | strand | +, -, or . |
| 7 | thickStart | coding start for display |
| 8 | thickEnd | coding end for display |
| 9 | itemRgb | 255,0,0 style RGB triplet |
| 10 | blockCount | number of exons or blocks |
| 11 | blockSizes | comma-separated lengths |
| 12 | blockStarts | comma-separated offsets from chromStart |
BED3, BED4, BED5, BED6, and BED12 are the named variants. Anything beyond column 12 is "BED12 plus" and is tool-specific, which is why bigBed files carry their own schema. A BED file with twelve columns and a 40-character name is roughly five times larger than the same intervals as BED3.
Where BED Sits Among Bioinformatics File Formats
Sizing a BED file makes sense only next to the other formats in a pipeline, because the same experiment produces BED, BAM, bigWig, and sometimes FASTQ from the same samples. The formats fall into three families.
Plain text, line-oriented. BED, GFF3, GTF, VCF, SAM, and FASTA. Size scales linearly with record count and record width. Compression works well because the text is repetitive.
Binary or compressed with an index. BAM, CRAM, BCF, bigBed, bigWig, and tabix-indexed BGZF files. Size is not predictable from line counts, but random access is possible, which changes memory behavior completely.
Sequence with quality scores. FASTQ and its trimmed derivatives.
The FASTQ versus FASTA distinction matters for storage planning because people routinely confuse a sequencing file with an annotation file. A FASTA record is a header line beginning with > followed by the sequence, often wrapped at 60 or 80 characters. A FASTQ record is exactly four lines: an @ header, the sequence, a + separator line, and a quality string of the same length as the sequence, with each quality character encoding a Phred score.
For a 150 bp read, a FASTA record carries about 150 bytes of sequence plus a header. A FASTQ record carries the same sequence plus 150 quality characters plus three extra newlines and the plus line, so the payload roughly doubles. A 30x human genome as FASTQ is on the order of 100 GB before compression. The same genome as a BED file of callable regions, peaks, or variants is megabytes. If your storage plan was built around FASTQ volumes, the BED files are never your bottleneck, but the BAM files next to them will be.
The BED File Size Calculator Formula
The calculator has one core equation and three corrections. The core equation is exact arithmetic, not an estimate:
raw_bytes = sum over all records ( sum of field lengths + (fields - 1) tabs + 1 line terminator )
Because the field count and field widths are usually stable across a file, this collapses to a usable planning formula:
raw_bytes ≈ N × (mean_field_bytes + separator_bytes)
where N is the record count, mean_field_bytes is the average total character count of all fields on a line, and separator_bytes is (fields - 1) for the tabs plus 1 for the newline. For a BED6 file that is 5 tabs plus 1 newline, so 6 bytes of pure overhead per line.
The three corrections are:
Compression. compressed_bytes ≈ raw_bytes / ratio, where the ratio must be measured on your own data, not borrowed.
Index overhead. A tabix or CSI index sits next to the compressed file and adds to the total footprint. Budget it as a separate line item and replace the guess with a measurement once you build one index.
Filesystem and storage-layer overhead. A file occupies whole disk blocks, not fractional ones. Object storage bills by object count as well as bytes. Small BED files multiplied by thousands of samples cost more than their byte total suggests.
For memory, the formula is:
peak_RAM ≈ uncompressed_bytes_of_loaded_file × structure_multiplier + fixed_overhead
The structure_multiplier is the honest unknown. Tools that stream, such as awk or bedtools with -sorted, have a multiplier near zero. Tools that build an interval tree or hash of one file hold several copies of the text in memory. A working rule of thumb is to budget 5 to 10 times the uncompressed text size of the file that gets loaded, then verify with /usr/bin/time -v on a subsample. Do not trust the rule of thumb over your own measurement.
Byte budget per BED column
When you have no file to measure, build the estimate from column widths. These are realistic budgets for human and mouse data:
| Field | Bytes to budget |
|---|---|
| chrom | 5 to 12 (chr1 is 4, chrUn_KI270442v1 is 16) |
| chromStart | 1 to 9 (human chromosomes reach 248,956,422) |
| chromEnd | 1 to 9 |
| name | 6 to 40 (peak IDs are short, variant IDs are long) |
| score | 1 to 4 |
| strand | 1 |
| thickStart plus thickEnd | 2 to 18 |
| itemRgb | 7 to 11 |
| blockCount | 1 to 3 |
| blockSizes | about 4 per block |
| blockStarts | about 4 per block |
| separators | equals the number of columns |
A BED6 peak line averages 35 to 55 bytes. A BED12 gene-model line averages 60 to 120 bytes, with the tail driven by exon counts. A BED12+ line from a segmentation pipeline with a long element name and extra metadata columns can exceed 200 bytes.
How to Calculate BED File Size: A Numbered Walkthrough
The example below uses peaks.bed, a BED6 file with 250,000 records. Every command is copy-paste ready and each one shows the output you should expect.
Step 1. Confirm the delimiter and line endings
head -n 3 peaks.bed | cat -A
Expected output:
chr1^I10012^I10455^IPeak_1^I500^I+$
chr1^I180422^I180901^IPeak_2^I430^I+$
chr1^I240118^I240560^IPeak_3^I610^I-$
^I marks a tab and $ marks the line end. If you see ^M$ instead, the file has Windows CRLF endings and every last field carries a stray carriage return. Fix it before anything else:
sed -i 's/\r$//' peaks.bed
If cat -A shows spaces where you expect ^I, the file is space-delimited and is not valid BED. Convert with sed -i 's/ /\t/g' only if you are certain no field (such as a name) contains a space.
Step 2. Count records correctly
wc -l peaks.bed
Expected output:
250000 peaks.bed
wc -l counts newline characters, so a file whose last line has no trailing newline is undercounted by one. Use awk when the exact count matters:
awk 'END {print NR}' peaks.bed
Step 3. Profile the column count
awk -F'\t' '{print NF}' peaks.bed | sort | uniq -c | sort -rn
Expected output for a clean BED6 file:
250000 6
Two or more rows in this output means the file has inconsistent column counts, which will break most parsers. Find the offending lines with:
awk -F'\t' 'NF != 6 {print NR": "NF" fields"}' peaks.bed | head
Step 4. Measure the true byte count
wc -c peaks.bed
Expected output:
10525000 peaks.bed
On macOS use stat -f %z peaks.bed. On Linux use stat -c %s peaks.bed. These report the same value as wc -c without reading the file into user space.
Step 5. Compute the mean record width
awk '{n++
b += length($0) + 1}
END {printf "records=%d bytes=%d mean=%.2f\n", n, b, b/n}' peaks.bed
Expected output:
records=250000 bytes=10525000 mean=42.10
The +1 accounts for the newline character. It overcounts by one byte on the final record, which is irrelevant at this scale.
Step 6. Project the new file size
Suppose the next run produces 1,200,000 peaks with the same column layout and similar coordinate magnitudes. The raw projection is:
awk -v n=1200000 -v m=42.10 'BEGIN {printf "raw=%.0f bytes (%.2f MB)\n", n*m, n*m/1e6}'
Expected output:
raw=50520000 bytes (50.52 MB)
Step 7. Measure the compression ratio on a representative slice
Do not use a generic ratio. Compress a slice from the middle of the file, because the first records are often atypical:
sed -n '100001,150000p' peaks.bed > slice.bed
wc -c slice.bed
gzip -6 -c slice.bed | wc -c
Expected output:
2105000
470013
That is a ratio of 2105000 / 470013 = 4.48. Project the compressed size:
awk -v raw=50520000 -v r=4.48 'BEGIN {printf "compressed=%.0f bytes (%.2f MB)\n", raw/r, raw/r/1e6}'
Expected output:
compressed=11276786 bytes (11.28 MB)
Compression ratio is content-dependent. A BED file with a few long chromosomes and highly repetitive block structure compresses better than one with thousands of short scaffolds and long random identifiers. A file with a long free-text metadata column compresses worse. Re-measure whenever the generator changes.
Step 8. Build the compressed file and its index
bgzip -@ 4 -c peaks.bed > peaks.bed.gz
tabix -p bed peaks.bed.gz
ls -l peaks.bed.gz peaks.bed.gz.tbi
Expected output:
-rw-r--r-- 1 user group 11280814 peaks.bed.gz
-rw-r--r-- 1 user group 184322 peaks.bed.gz.tbi
bgzip produces blocked gzip, which is what tabix and random-access readers require. Plain gzip output cannot be indexed and cannot be queried by region. The tabix index size is not a fixed fraction. It stores offsets for occupied bins plus a linear index with one entry per 16 kb window across each chromosome, so it scales with genome span and with the number of distinct positions covered. As a starting budget, reserve 2 to 10 percent of the compressed size, then replace the guess with your measured number.
Step 9. Measure real memory use
/usr/bin/time -v bedtools intersect -a peaks.bed -b genes.bed > overlap.bed
Expected key lines:
Maximum resident set size (kbytes): 412500
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:12.44
412,500 KB is about 403 MB. Compare that to the uncompressed size of the file the tool held in memory and you have your structure multiplier for this data shape and this tool. Scale the measurement by record count for planning, then add 20 percent headroom.
Step 10. Verify the estimate against reality
ls -l peaks.bed
du -b peaks.bed
du -h peaks.bed
ls -l and du -b report apparent size in bytes. du -h reports allocated blocks, which will be rounded up and is usually slightly larger. When the estimate and the measurement disagree by more than a few percent, the cause is almost always a column count you did not account for or a slice that was not representative.
Step 11. Sort without running out of disk
Sorting is where scratch space appears. GNU sort spills to a temporary directory when the input exceeds the buffer:
sort -k1,1 -k2,2n --parallel=4 -S 2G -T /scratch/tmp peaks.bed > peaks.sorted.bed
Budget scratch disk at roughly two to three times the uncompressed input size, because sort writes runs and then merges them. If /scratch is small, the job fails with sort: cannot create temporary file in /scratch/tmp or fills the partition silently. bedtools sort behaves similarly and accepts -mem and -tmpdir.
Two Worked Examples
Example A: ChIP-seq peak file, BED6
Inputs: 250,000 peaks, six columns, mean record width 42.10 bytes, gzip ratio 4.48 measured on a 50,000-record slice.
| Stage | Calculation | Result |
|---|---|---|
| Raw | 250,000 × 42.10 | 10.53 MB |
| BGZF, level 6 | 10.53 / 4.48 | 2.35 MB |
| Tabix index | measured | 0.18 MB |
| Total on disk | sum | 2.53 MB |
This file will load into memory in well under a second with any tool. On a laptop it is invisible. Multiplied across 5,000 samples it becomes 12.6 GB, which is still manageable but no longer invisible.
Example B: Whole-genome segmentation, BED12 plus metadata
Inputs: 1,200,000 records, twelve columns plus two metadata columns, mean record width 168.4 bytes, gzip ratio 5.9 measured on a viable slice.
| Stage | Calculation | Result |
|---|---|---|
| Raw | 1,200,000 × 168.4 | 202.08 MB |
| BGZF, level 6 | 202.08 / 5.9 | 34.25 MB |
| Tabix index | measured | 2.1 MB |
| In-memory interval tree | 202.08 × 6 | 1,213 MB |
| Peak RAM with 20 percent headroom | 1,213 × 1.2 | 1,456 MB |
The compressed file fits on a laptop. The in-memory representation does not fit in a 1 GB container. That gap is exactly why storage and memory must be estimated together.
Parameters That Move the Estimate
| Parameter | Effect on size | How to measure it | ||
|---|---|---|---|---|
| Column count | adds 1 byte per extra column plus the new content | `awk -F'\t' '{print NF}' \ | sort \ | uniq -c` |
| Coordinate magnitude | 1 digit per order of magnitude per coordinate | tail -n 1 and read the largest numbers | ||
| Sequence names | chr1 is 4 bytes, a 16-byte scaffold name is 4x larger | `cut -f1 \ | awk '{s+=length($0)} END {print s/NR}'` | |
| Name field | often the widest field in real files | `cut -f4 \ | awk '{s+=length($0)} END {print s/NR}'` | |
| Score precision | 500 is 3 bytes, 500.123 is 7 | inspect the score column | ||
| BED12 block lists | blockSizes and blockStarts grow with exon count | compare bwa-style vs full-model files | ||
| Line ending | CRLF adds 1 byte per line and breaks parsers | cat -A or file peaks.bed | ||
| Compression level | level 1 is fast and about 15 to 25 percent larger than level 6 | gzip -1 vs gzip -6 on a slice | ||
| Index type | CSI costs more than TBI and handles large chromosomes | ls -l the index you built | ||
| Sort order | sorted files compress better than shuffled ones | compress both and compare |
Storage Options Beyond Gzipped Text
Plain gzip is the default because it is universal. Three alternatives change the arithmetic.
BGZF plus tabix. Slightly larger than plain gzip because of the block structure, and in exchange you get region queries. A query for one locus on a 30 MB indexed BED file reads kilobytes, not the whole file.
bigBed. A binary indexed format converted from BED with bedToBigBed. It is substantially smaller than gzipped BED for most annotation data, supports remote range requests over HTTP, and is what genome browsers prefer to load. The tradeoff is that it needs a chromosome size file and a schema, and it is not human-readable.
Columnar formats. Parquet and Arrow-based tables are common for cross-sample matrices of intervals. They compress well and allow column pruning, but the ecosystem of interval tools mostly expects BED, so conversion is an extra step.
For anything shipped to collaborators, gzipped BED plus a tabix index is the safest default. For anything rendered in a browser, bigBed or bigWig wins on both size and latency.
Common Errors and What They Mean
cat -A shows ^M$. Windows line endings. The last field on every line ends with a carriage return, so chr1 becomes chr1\r and matches nothing. Fix with sed -i 's/\r$//' file.bed.
Error: Sorted input specified, but the file peaks.bed has the following out of order record. bedtools with -sorted requires both inputs to be sorted by chromosome and then by start. Sort with sort -k1,1 -k2,2n and confirm the chromosome order matches between files. Note that lexical sort puts chr10 before chr2. If the companion file uses a natural chromosome order, extract it with cut -f1 file.bed | uniq and pass that order where the tool supports it.
[W::hts_idx_load3] The index file is older than the data file. The .tbi predates the current .bed.gz, usually because the BED was regenerated and tabix was not rerun. Rebuild the index. This warning means queries return stale results.
gzip: peaks.bed.gz: not in gzip format. The file is plain text with a .gz extension, or it is a partial download. Check with file peaks.bed.gz.
awk: can't open file peaks.bed. Wrong directory, or the file is named peaks.bed.gz. Pipe it: zcat peaks.bed.gz | awk '...'.
Mixed column counts. A file that is BED6 for 99 percent of lines and BED12 for the rest will parse differently depending on the tool. Normalize to a single schema before sizing, because the estimate assumes uniformity.
No space left on device during sort. Scratch space ran out. Rerun with -T pointing at a larger filesystem.
Blank or comment lines. wc -l counts them, and awk -F'\t' reports column counts of 1. BED has no comment convention, so a # header line is technically invalid. Strip it with grep -v '^#' or grep -v '^track' before sizing and before feeding interval tools.
Common Mistakes and Limitations
Estimating from head -n 1000. Early records in sorted files cluster at the start of chromosome 1, where coordinates have fewer digits and where peak density may differ from the genome average. Sample from the middle with sed -n and sample more than you think you need.
Forgetting the sidecar files. A BED file in production is often three files: the BED, the BGZF copy, and the index. Some pipelines keep all three plus a sorted variant. Multiply accordingly. The same applies to .tbi and .csi files that survive after the source BED is deleted.
Using apparent size for capacity planning. du -b gives apparent size and du -h gives allocated size. On a filesystem with 4 KB blocks, 10,000 small BED files of 500 bytes each consume 40 MB, not 5 MB. On object storage, per-object request costs can exceed the byte costs.
Assuming a compression ratio transfers. Ratios depend on alphabet, repetition, and field entropy. A ratio measured on peak calls does not transfer to variant annotation BED files with long IDs, and it certainly does not transfer to files containing long free-text columns.
Forgetting that BGZF is larger than gzip. Blocked compression costs a small amount of space. Do not be surprised when the indexed file exceeds the plain gzip you tested.
Treating memory as proportional to file size. It is proportional to record count and structure complexity. A file of 1 million short records can use more RAM than a file of 200,000 long records because the per-record overhead dominates.
Ignoring the sort buffer. A bedtools intersect that runs fine on 500,000 intervals on a laptop fails on 50 million in a container with a 4 GB limit. The fix is -sorted on pre-sorted inputs, which streams instead of building a structure.
Assuming the estimate is a guarantee. These formulas predict within a few percent for uniform files and by tens of percent for files with variable record widths. Measure one real file and use the measurement.
Coordinate convention errors masquerading as size problems. A file that looks suspiciously large may simply have been written with 1-based inclusive coordinates by a tool that expected otherwise, producing an extra base per interval and downstream mismatches. Verify the convention before you scale anything.
These methods work on files, not on individual experiments. A single unusual dataset needs its own measurement, and the arithmetic above is a starting point rather than a substitute for profiling.
How to Confirm Your Estimate Is Right
Run four checks on every production BED you create.
- Byte comparison. Compare
wc -cagainst the predicted raw size. Agreement within 2 percent means the column profile assumption held. - Record count comparison. Compare
awk 'END {print NR}'against the predicted record count. Divergence means the generator produced a different number of intervals than expected, which is a pipeline question, not a storage question. - Column profile. Confirm every record has the same
NF. Inconsistent counts invalidate the estimate and usually indicate a formatting bug upstream. - Index freshness. Confirm the
.tbior.csitimestamp is newer than the compressed file. Usels -l, time-style=full-isoand compare.
For a file destined for a shared cluster, add a fifth check: run the actual analysis command under /usr/bin/time -v on a 10 percent subsample, read Maximum resident set size, and multiply. That single measurement beats any rule of thumb.
Frequently Asked Questions
How do I calculate the size of a BED file?
Multiply the number of records by the mean bytes per record, including tabs and the newline, then divide by the compression ratio you measure on a slice of the same data. Add index and filesystem overhead separately.
How many bytes is one BED line?
A BED6 line is typically 35 to 55 bytes and a BED12 line is typically 60 to 120 bytes. The exact value comes from summing the field widths plus one byte per column for separators.
How much does gzip reduce a BED file?
Commonly a factor of 3.5 to 6, driven by coordinate repetition and how many distinct chromosomes the file spans. Measure your own ratio with gzip -6 -c slice.bed | wc -c because borrowed ratios mislead.
How much RAM does bedtools intersect need for a BED file?
It depends on how many records the tool holds in memory and its interval structure, usually several times the uncompressed text size of the loaded file. Use -sorted with pre-sorted inputs to stream, and confirm with /usr/bin/time -v.
What is the difference between BED3, BED6, and BED12?
BED3 has coordinates only, BED6 adds name, score, and strand, and BED12 adds thickStart, thickEnd, itemRgb, blockCount, blockSizes, and blockStarts. Column count drives file size more than record count does.
Is a BED file the same as a FASTA or FASTQ file?
No. FASTA and FASTQ store nucleotide sequences, with FASTQ adding per-base quality scores. BED stores intervals on a reference, so a BED file for a whole genome is megabytes while the same genome as FASTQ is many gigabytes.
Why is my BED file larger than the estimate?
The usual causes are more columns than assumed, longer name or metadata fields, CRLF line endings, BGZF block overhead, and sidecar index files counted into the total.
Can I store a BED file as bigBed to save space?
Yes, and bigBed also gives you indexed random access and remote range requests. It requires a chromosome size file and a schema, and it is binary, so keep the original BED if you need a human-readable copy.
Further Reading
- A prediction method for the size distribution of saltating particles above a mixed-size bed
- Calculation method of the contact stiffness of bed-foundation interfaces considering foundation creep
- The Size Distribution of Carrying Capacity in the Non-Uniform Suspended Load Capacity Calculation Method and its Verification
- Three-zonal engineering method of heat calculation for fluidized bed furnaces based on data on commercial investigations of heat generation distribution during biomass combustion
- Revised drag calculation method for coarse grid Lagrangian-Eulerian simulation of gas-solid bubbling fluidized bed
- Defining and Optimising High-Fidelity Models for Accurate Inherent Strain Calculation in Laser Powder Bed Fusion