BED vs VCF: Key Differences
By Dr. Zubair Khalid, DVM, MS, PhD ·

BED is a plain-text format for genomic intervals and VCF is a plain-text format for genomic variants, so the core difference is what a single row means: a stretch of the genome versus an allele at a position. Use BED when your question is "where in the genome," and use VCF when your question is "what changed, and in which sample."
That rule of thumb resolves most real decisions. The rest of this guide covers the coordinate conventions that cause off-by-one errors, the fields each format can and cannot hold, the tools that expect each one, and the conversions that lose information without warning.
Side-by-Side Comparison Table
The table below compares the two formats on the dimensions that change pipeline design rather than the ones that only matter for display.
| Dimension | BED | VCF |
|---|---|---|
| Full name | Browser Extensible Data | Variant Call Format |
| Origin and steward | UCSC Genome Browser, which publishes the reference description | 1000 Genomes Project, now maintained by GA4GH with versioned specifications |
| Unit of a record | An interval | An allele at a reference position |
| Coordinate convention | 0-based start, exclusive end | 1-based position of the first REF base |
| Minimum columns | 3 | 8 |
| Reference allele | Not representable | Required (REF) |
| Alternate allele | Not representable | Required (ALT), comma-separated if multiple |
| Genotypes and samples | Not supported | FORMAT column plus one column per sample |
| Quality | Optional score column with context-dependent meaning | QUAL and FILTER columns with defined semantics |
| Header | Optional track, browser, and comment lines | Mandatory meta lines plus a #CHROM line |
| Binary or indexed form | bigBed, or bgzip plus tabix | BCF, or bgzip plus tabix |
| Typical producers | MACS2, bedtools, UCSC utilities, target-design tools | GATK, DeepVariant, bcftools, Strelka |
| Typical consumers | bedtools, featureCounts, IGV, UCSC Browser | VEP, ANNOVAR, bcftools, clinical reporting pipelines |
| Formal validation | No schema and no validator | Versioned spec with a validator |
| Information lost in conversion | Everything about alleles, genotypes, and quality | Nothing, if coordinates and REF/ALT are retained |
Two rows in that table cause most of the trouble in practice: the coordinate convention and the presence or absence of alleles. Nearly every other difference follows from those two.
What BED Is
BED stands for Browser Extensible Data. It was created at the University of California, Santa Cruz for the UCSC Genome Browser, and the UCSC documentation remains the de facto description of the format. A BED file is tab-delimited text with one interval per line and no mandatory header. The simplest valid file is three columns wide.
The Three Required Columns and the 0-Based Coordinate System
The minimum BED record has three fields:
- chrom: the sequence name, such as chr1 or chrX
- chromStart: the 0-based start coordinate
- chromEnd: the end coordinate, which is not included in the interval
chr1 1000 1500
chr1 2000 2100
chrX 73800000 73801000
The half-open convention means the interval chr1 1000 1500 covers 1-based bases 1001 through 1500 and has a length of 500 bp, computed as end minus start. A single base is written with a start and end that differ by one, so the base at 1-based position 1000 is chr1 999 1000.
This is the single most common source of bugs when moving between formats. A variant reported at VCF position 1000 occupies the BED interval 999 to 1000, not 1000 to 1001. The error is invisible in small tests and systematic across a whole file.
Optional Columns and BED12
BED is extensible by design, and columns 4 through 12 have conventional meanings:
- chrom
- chromStart
- chromEnd
- name
- score, defined by UCSC as an integer from 0 to 1000
- strand, either + or -
- thickStart
- thickEnd
- itemRgb
- blockCount
- blockSizes
- blockStarts
BED3, BED6, and BED12 are the common stopping points. BED12 encodes a spliced gene model as a set of blocks, which is how the Genome Browser draws transcripts with introns. A BED12 record for a three-exon transcript lists the exon sizes in blockSizes and their offsets from chromStart in blockStarts, both as comma-terminated lists.
Files may also contain comment lines beginning with #, a track line that sets display parameters, and a browser line that sets the view window. These lines are legal in the browser and illegal in many parsers. bedtools will complain about a track line unless you strip it first or pass the right option.
BED Dialects You Will Meet
BED is less a single format than a family of conventions. The dialects matter because a parser that assumes one meaning will silently produce wrong numbers.
- narrowPeak and broadPeak from MACS2 and other peak callers are BED6 plus four extra columns holding fold enrichment, the negative log10 p-value, the negative log10 q-value, and the peak summit offset. Column 5 in a narrowPeak file is fold enrichment, not the UCSC 0 to 1000 score.
- bedGraph has four columns (chrom, start, end, value) and is used for coverage and signal tracks. It converts to bigWig with bedGraphToBigWig, which requires a chrom.sizes file and a coordinate-sorted input.
- bigBed is a binary, indexed form of BED created with bedToBigBed. It supports random access over the network and is what the UCSC Browser serves for large tracks. Creating one requires sorted input and a chrom.sizes file listing every sequence and its length.
- BEDPE stores paired intervals, which is how structural variants and Hi-C contacts are often represented.
The practical consequence is that a BED file carries no self-description. Two files with twelve columns can mean different things, and nothing inside the file tells you which.
What VCF Is
VCF stands for Variant Call Format. It was developed for the 1000 Genomes Project and is now maintained by the Global Alliance for Genomics and Health (GA4GH), which publishes versioned specifications covering versions 4.0 through 4.4. A VCF file describes variants relative to a reference genome and can carry per-sample genotypes.
Header, Fixed Columns, and 1-Based Positions
Every VCF begins with meta-information lines that start with ##, then a single header line that starts with #CHROM. The first meta line must declare the version.
##fileformat=VCFv4.2
##reference=file:///ref/GRCh38.fa
##contig=<ID=chr1,length=248956422>
##INFO=<ID=DP,Number=1,Type=Integer,Description="Total Depth">
##FORMAT=<ID=GT,Number=1,Type=String,Description="Genotype">
#CHROM POS ID REF ALT QUAL FILTER INFO FORMAT NA12878
chr1 1000 rs123 A G 50 PASS DP=30 GT:AD:DP:GQ 0/1:15,15:30:99
The eight fixed columns are:
- CHROM: the sequence name
- POS: the 1-based position of the first base of REF
- ID: a database identifier such as a dbSNP rs number, or a dot
- REF: the reference allele
- ALT: a comma-separated list of alternate alleles
- QUAL: a Phred-scaled quality score
- FILTER: PASS or a code defined in a ##FILTER header line
- INFO: a list of key=value annotations
Everything after INFO is optional and describes samples. FORMAT names the per-sample fields, and each sample column follows that same order.
The 1-based POS is the direct counterpart to BED's 0-based start. A SNV at POS 1000 with REF A and ALT G corresponds to the BED interval chr1 999 1000.
Indels, Genotypes, and the FORMAT Column
VCF requires a padding base for simple indels. A deletion of a T at position 1001 from reference sequence AT is written as POS 1000, REF AT, ALT A. The anchor base at 1000 is unchanged, and the deleted base is the second character of REF. This is not a formatting quirk. It is how the format keeps every record anchored to a reference position so that the record has a defined location even when the variant is an insertion or a deletion.
The FORMAT column carries sample-level data:
- GT: genotype, written 0/0, 0/1, 1/1, or ./. for missing, with a pipe such as 1|0 for phased data
- AD: allelic depths, reference first and then each ALT in order
- DP: read depth at the site
- GQ: genotype quality
- PL: Phred-scaled genotype likelihoods
Genotypes are the reason VCF exists. A BED interval can tell you that a region is a called peak or a capture target. It cannot tell you that sample A is heterozygous and sample B is homozygous reference at a specific base.
Symbolic Alleles, gVCF, and BCF
Structural variants use symbolic ALT alleles such as <DEL>, <INS>, <DUP>, <INV>, and <CNV>, with breakend notation for junctions. A gVCF adds reference blocks marked with <NON_REF> and an END key in INFO, which lets a caller record evidence for the reference genotype across the whole genome rather than only at variant sites. Joint genotyping across a cohort depends on that reference coverage.
BCF is the binary, compressed counterpart of VCF. It is produced and consumed by bcftools and htslib and is the sensible choice for large cohorts. A bgzipped VCF indexed with tabix gives random access by region. Plain gzip does not, because tabix needs the BGZF block structure to seek.
Normalization
Two VCFs can describe the same variant differently. Left-alignment shifts an indel as far left as it can go in a repeat region, and trimming removes bases that are identical between REF and ALT. bcftools norm -f reference.fa performs both. Without normalization, the same deletion can appear at several positions across samples, and a comparison or a merge will treat them as distinct variants. Normalize before comparing, merging, or counting.
The Same Event Written Two Ways
Concrete examples remove most of the ambiguity. Take a reference sequence of ACGTACGTAC on chr1, with bases numbered from 1.
A SNV at position 4 (T to A). The VCF record is CHROM chr1, POS 4, REF T, ALT A. The BED interval is chr1 3 4. The BED start is POS minus one, and the end equals POS.
A deletion of the C at position 3. The VCF record is POS 2, REF CG, ALT C. The BED interval covering the deleted base is chr1 2 3. Note that the BED interval does not cover the anchor base at position 2. It covers only the base that is absent from the sample.
An insertion of TT after position 5. The VCF record is POS 5, REF C, ALT CTT. There is no single BED interval for an insertion, because an insertion adds sequence between two reference bases rather than replacing a reference base. The closest BED representation is the zero-width or one-base anchor point, and that representation has already lost the inserted sequence.
A region rather than a variant. A capture target spanning positions 1001 to 2000 is chr1 1000 2000 in BED. There is no natural VCF representation, because nothing has changed.
Those four examples cover the whole conceptual gap. BED answers positional questions. VCF answers allelic questions. Insertions are the case where the gap is widest, because an insertion has a position but no reference span.
Where BED and VCF Fit Among Bioinformatics File Formats
Bioinformatics file formats sit in layers, and knowing the layer tells you what a file can answer.
- FASTA holds sequences with a header line beginning with >. Reference genomes and protein sets are distributed this way.
- FASTQ holds raw reads with per-base quality scores, four lines per record: an @header, the sequence, a + separator, and a quality string of the same length as the sequence. Quality characters are ASCII-encoded Phred scores offset by 33.
- SAM, BAM, and CRAM hold alignments of reads to a reference. BAM is the binary form and CRAM is the reference-compressed form.
- BED, GFF, and GTF hold intervals and annotations.
- VCF and BCF hold variants.
- bedGraph and bigWig hold signal tracks.
FASTQ vs FASTA in One Paragraph
FASTQ stores raw instrument output with per-base quality scores. FASTA stores sequences without quality scores and is used for references, transcripts, and proteins. Neither carries genomic coordinates, which is why neither can answer a positional question without an alignment step in between. A FASTQ file becomes a BAM through an aligner, and only then do BED and VCF become possible.
The Usual Pipeline Order
Reads go from FASTQ to BAM through an aligner such as BWA or STAR. From the BAM, work splits two ways. Interval work (peak calling, coverage, target overlap, counting reads in regions) produces BED. Variant work (variant calling, genotyping, annotation, filtering) produces VCF. Many pipelines do both and join them at the end, for example by intersecting a peak BED with a variant BED to ask whether a regulatory region contains a called variant.
The formats are frequently mixed inside a single script. One Python package, pygenomics, reads and writes BAM, BED, GFF3, and VCF and provides interval operations across all of them, which reflects how routine that mixing has become [1].
When to Use BED
Use BED when the record you need to store is a region. Concrete cases:
- Peak calls from ChIP-seq, ATAC-seq, or CUT&RUN
- Target regions for exome or panel capture
- Blacklists of problematic or high-signal regions
- Promoter, enhancer, or gene body sets for overlap analysis
- Coverage and signal tracks in bedGraph form
- Windows for computing metrics such as GC content or mappability
- Masked regions from repeat annotation or callable-region analysis
- Any list you plan to feed to bedtools, featureCounts, or the UCSC Genome Browser
BED is also the right intermediate when you want to count variants per feature. Convert the VCF to intervals, intersect with a gene BED, and count. The variant-level detail is not needed for that operation, and carrying it through adds work without adding information.
When to Use VCF
Use VCF when the record you need to store is an allele, especially when sample identity matters. Concrete cases:
- Germline variant calls from GATK, DeepVariant, or bcftools
- Somatic variant calls with tumor and normal genotypes
- Joint genotyping across a cohort
- Variant annotation with VEP, ANNOVAR, or SnpEff
- Filtering on QUAL, depth, genotype quality, or allele balance
- Clinical reporting of a specific variant with its HGVS description
- Input to GWAS or burden testing
- Phasing and haplotype analysis
VCF is also the only one of the two formats with a defined FILTER field, which matters when you need to record why a variant was excluded rather than simply dropping it. Keeping filtered records with their reasons is often the difference between a reproducible analysis and one that cannot be audited.
BED vs VCF: Five Criteria That Decide the Choice
1. The Unit of Data
If one row should mean "this stretch of the genome," use BED. If one row should mean "this allele at this position," use VCF. Most confusion disappears once you can state which of those two sentences describes your data.
2. Alleles and Genotypes
If you need REF, ALT, genotype, allele depth, or phasing, you need VCF. BED has no field for any of them. You can approximate a variant in BED as a one-base interval, and people do, but the ALT allele and the sample identity are gone. A file of variant positions is not a variant file in any useful sense once someone asks which allele was observed.
3. Downstream Tool Expectations
bedtools, featureCounts, MACS2, and the UCSC Browser expect BED or a BED-like dialect. GATK, bcftools, VEP, ANNOVAR, and Strelka expect VCF. Choosing the format your main tool wants avoids a conversion step that can introduce errors. When two tools disagree, convert at the boundary and keep the original file.
4. Access Pattern and File Size
BED files of intervals are small and cheap to sort and merge. VCF files of whole-genome calls are large, and cohort VCFs are very large, which is why BCF and region-indexed access exist. If you only need regions, carrying a full VCF through the pipeline wastes time and disk. If you need to query one locus across a thousand samples, a BCF with a CSI index is the right structure.
5. Validation and Provenance
VCF has a versioned specification, defined header semantics, and a validator. BED has documentation and conventions but no formal schema, so two BED files with the same column count can mean different things. When provenance matters, VCF carries more of it in the header, including the reference build and the definitions of every INFO and FORMAT key.
Worked Scenarios
Scenario 1: ChIP-seq Peak Calling
MACS2 writes a narrowPeak file, which is BED6 plus four columns. Sort it, then intersect with a promoter set.
macs2 callpeak -t chip.bam -c input.bam -f BAM -g hs -n run1
sort -k1,1 -k2,2n run1_peaks.narrowPeak > run1.peaks.sorted.bed
bedtools intersect -a run1.peaks.sorted.bed -b promoters.bed -wa -u > peaks_in_promoters.bed
No VCF appears anywhere in this workflow. There are no alleles to record and no samples to genotype. The output is a set of regions with a significance value attached.
Scenario 2: Germline Variant Calling
GATK HaplotypeCaller emits a gVCF per sample. GenomicsDBImport and GenotypeGVCFs combine them into a cohort VCF. Normalize, filter, and index.
bcftools norm -m -any -f GRCh38.fa cohort.vcf.gz -Oz -o cohort.split.vcf.gz
bcftools view -i 'QUAL>=30 && INFO/DP>=10' cohort.split.vcf.gz -Oz -o cohort.filtered.vcf.gz
tabix -p vcf cohort.filtered.vcf.gz
The output stays VCF because genotypes, quality, and filter status are the point of the analysis. Converting to BED at this stage would discard the reason the pipeline exists.
Scenario 3: Defining Capture Targets
A target BED defines the regions to sequence. Pad it before computing coverage so that reads overlapping the edges are counted.
bedtools slop -i targets.bed -g hg38.chrom.sizes -b 100 > targets.pad100.bed
Watch the coordinate convention at this boundary. Picard interval_list files are 1-based and closed, while BED is 0-based and half-open. GATK accepts both, and mixing them shifts every target by one base, which is small enough to survive review and large enough to change edge coverage.
Scenario 4: Counting Variants per Gene
Convert the VCF to intervals with bcftools query, then intersect with a gene BED.
bcftools query -f '%CHROM\t%POS0\t%END\t%REF\t%ALT\n' sample.vcf.gz > sample.variants.bed
bedtools intersect -a sample.variants.bed -b genes.bed -wa -wb | cut -f4,5,9 | sort | uniq -c
%POS0 gives the 0-based position and %END gives the end of the REF allele, so the conversion handles deletions correctly. A naive conversion that writes POS to POS+1 would misplace every deletion by the length of its REF allele.
Scenario 5: Somatic Variant Tracking
A tumor-normal pair produces a VCF with two sample columns. Allele balance comes from FORMAT/AD, and the tumor and normal columns are compared directly.
bcftools query -f '%CHROM\t%POS\t%REF\t%ALT[\t%SAMPLE=%GT:%AD]\n' pair.vcf.gz
A BED file cannot represent this. The same coordinate appearing in two samples with different genotypes is exactly the information BED drops.
Converting Between BED and VCF
VCF to BED
This direction is routine and lossy in a controlled way. You keep coordinates and optionally REF and ALT, and you drop genotypes, quality, and filter status.
Three common routes:
- bcftools query with %POS0 and %END, as in Scenario 4.
- vcf2bed from BEDOPS, which writes a BED record per VCF line.
- bedtools intersect with a VCF as the -a file, which reads VCF records as intervals directly.
For structural variants, %END follows the INFO/END key when it is present, which is what you want for deletions and copy number variants. For symbolic alleles without an END key, the interval collapses to the anchor base and you need to handle it explicitly.
BED to VCF
This direction is where people get hurt. A BED interval does not contain an ALT allele, a genotype, a quality score, or a sample name. Converting BED to VCF requires you to supply the reference sequence and to decide what the ALT is. For a one-base interval you can look up the reference base and invent a variant, but you are inventing it.
The honest uses of BED to VCF are narrow:
- Building a site-only VCF with no genotypes, to restrict a caller to known positions
- Creating a truth set from a curated list where the alleles are already known from another source
- Formatting a list of positions for a tool that only accepts VCF
If you need to go this direction, generate the VCF with a script, then run bcftools norm -f reference.fa to check that REF matches the reference. A mismatch means your BED and your FASTA are on different builds or use different chromosome names.
Reading and Writing Both Formats in Code
Most scripting languages have libraries for both formats, and the choice of library matters less than the coordinate handling.
In Python, pysam wraps htslib and exposes BED and VCF readers with the coordinate conventions preserved. pygenomics provides interval operations across BAM, BED, GFF3, and VCF in pure Python with no compiled dependencies, which is convenient for pipelines that need to run in restricted environments [1].
In R, rtracklayer and GenomicRanges work with BED and VCF, and the Bioconductor convention is 1-based and closed, matching neither BED nor VCF exactly. That mismatch is a common source of silent errors when R code reads a BED file and treats the start as 1-based.
On the command line, bcftools handles VCF and BCF, bedtools handles BED and BED-like files, and htslib provides bgzip and tabix for both. The pairing is consistent: bcftools for alleles, bedtools for intervals.
Common Mistakes and Limitations
Off-by-one errors at every boundary. The VCF position 1000 to BED 999 to 1000 conversion is the canonical example, and the reverse conversion is equally easy to get wrong. Write one test case with a known variant and check it in both formats before running a batch.
Treating POS as the whole variant. For a deletion with a REF of length 5, the affected bases run from POS to POS+4. Using POS alone as a one-base interval undercounts every indel.
Losing the ALT allele silently. A BED file of variant positions looks fine until someone asks which allele was observed. Keep the original VCF and record the conversion in the pipeline log.
Chromosome naming mismatches. chr1 and 1 are different strings to every tool. chrM and MT are the same sequence under different names. Normalize names before any intersect, or you will get zero overlaps and conclude that there are no variants.
Genome build mismatches. Coordinates are meaningless without a build. hg19 and GRCh38 positions for the same variant differ, and a BED built on one build intersected with a VCF from another produces results that look plausible and are wrong.
gzip instead of bgzip. tabix and bcftools index require BGZF. A file compressed with plain gzip will fail to index, and the error message does not always make the cause obvious.
Unsorted input. bedtools merge and tabix both require sorting by chromosome and then by start. The command sort -k1,1 -k2,2n is the usual fix, but it sorts chromosome names lexicographically, which puts chr10 before chr2. Use a genome-order sort when the tool cares about order.
Multi-allelic records. One VCF line with ALT A,G counts as one record but two alleles. Splitting with bcftools norm -m -any before counting avoids both double counting and undercounting, depending on which mistake you would otherwise make.
FILTER values that are not PASS. A dot means no filter was applied, which is not the same as PASS. Filtering on FILTER="PASS" silently drops every unfiltered record.
Assuming BED column 5 is a score from 0 to 1000. In a narrowPeak file it is fold enrichment. In a bedGraph the fourth column is the value and there is no score column at all.
Leaving track and browser lines in a BED file. These are valid for the Genome Browser and invalid for most parsers. Strip them at the start of a pipeline rather than debugging the failure later.
Acronym collisions. A literature search for "BED" in a biomedical context returns the BED capture enzyme immunoassay used in HIV incidence estimation alongside genome interval papers [2]. Searching for "BED file format" or "BED tracks UCSC" narrows it quickly. VCF has fewer collisions but is sometimes confused with variant calling itself, which is a process rather than a format.
BED has no formal validator. Two files with twelve columns can carry different semantics. Document the dialect in the filename or a README, especially for narrowPeak and custom BED12 derivatives.
Limitations of this comparison. The right choice depends on your tools, your reference build, and what your downstream analysis actually needs. Test any conversion on a small subset with known answers before running it on a cohort, and treat any unexpected zero-overlap result as a coordinate or naming problem first.
Frequently Asked Questions
Is BED 0-based or 1-based?
BED is 0-based for the start coordinate, and the end coordinate is exclusive. The interval chr1 100 200 covers 1-based bases 101 through 200.
Can I convert a VCF to BED without losing information?
No. A VCF to BED conversion keeps coordinates and can keep REF and ALT, but it drops genotypes, quality scores, filter status, and sample identity. Keep the original VCF if any of those matter.
Which format should I use for a list of genomic regions?
Use BED. It is the native input for bedtools, featureCounts, and the UCSC Genome Browser, and it needs only three columns to be valid.
Do I need both BED and VCF in a variant calling workflow?
Often yes. The VCF holds the calls, and a BED file usually defines the target regions, the callable regions, or the blacklist that constrains calling and filtering.
How is BED different from GFF or GTF?
GFF and GTF are 1-based and closed, and they carry feature types and hierarchical relationships such as gene, transcript, and exon. BED is 0-based and half-open and is designed for intervals rather than for feature ontologies.
What is the difference between FASTQ and FASTA?
FASTQ stores raw reads with per-base quality scores, and FASTA stores sequences without quality scores. FASTQ is sequencing output and FASTA is a reference or protein set. BED and VCF are both downstream of the alignment step that connects them.
Further Reading
- Comparison of three rapid diagnostic tests for bloodstream infections using Benefit-risk Evaluation Framework (BED-FRAME)
- Comparison between fixed fluidized bed (FFB) and batch fluidized bed reactors in the evaluation of FCC catalysts
- Comparison between MAT Flow Fixed Bed and Batch Fluidized Bed Reactors in the Evaluation of FCC Catalysts. 1. Conversion and Yields of the Main Hydrocarbon Groups
- Comparison between MAT Flow Fixed Bed and Batch Fluidized Bed Reactors in the Evaluation of FCC Catalysts. 2. Naphtha Composition