# BED vs GFF vs GTF: Which Genomic Annotation Format Should You Use?

BED records bare intervals in 0-based, half-open coordinates, while GFF and GTF record named features in 1-based, closed coordinates and can encode a gene-to-transcript-to-exon hierarchy. That is the difference in one sentence. The rule of thumb follows from it: use BED when a tool needs a list of regions and nothing more, and use GTF or GFF3 when the question involves genes, transcripts, exons, strands, reading frames, or parent-child relationships.

All three formats are tab-delimited plain text. All three describe positions on a reference assembly. A BED file of genes and a GTF file of the same genes can cover exactly the same bases. What separates them is the coordinate convention, the mandatory fields, and whether the file can say what a region is. Those differences are where silent errors live. A coordinate mix-up does not crash a pipeline. It shifts every interval by one base, or makes an intersection return zero rows, and the run finishes with a plausible-looking table of wrong numbers.

Modern tools often accept all three. PanDepth, a coverage and depth calculator published in 2024, takes sorted or unsorted BAM or CRAM alignments together with GTF, GFF, or BED interval files, or a fixed window size [1]. That flexibility is convenient at the level of "a list of regions". It disappears the moment a downstream step needs to know that an interval is the third exon of one specific transcript.

## What Each Format Actually Stores

The three formats were designed for different jobs, and each one carries the scars of its origin. BED grew out of the [UCSC Genome Browser](/knowledge/bioinformatics/ucsc-genome-browser-beginner-tutorial-and-tips). GFF grew out of shared annotation efforts across model organism databases. GTF grew out of the transcript-centric needs of gene prediction and expression analysis. Knowing that history explains most of the practical differences.

### BED: intervals first

BED stands for Browser Extensible Data. It is a plain-text, tab-delimited format with a minimum of three columns and a maximum of twelve. The first three columns are mandatory and the rest are optional, which means two BED files with the same extension can carry wildly different information.

| Column | Name | Meaning |
|--|--|--|
| 1 | chrom | Chromosome or contig name |
| 2 | chromStart | 0-based start position |
| 3 | chromEnd | End position, exclusive |
| 4 | name | Free-text label |
| 5 | score | Display score from 0 to 1000 |
| 6 | strand | +, -, or . |
| 7 | thickStart | Start of the thick display region |
| 8 | thickEnd | End of the thick display region |
| 9 | itemRgb | R,G,B color value |
| 10 | blockCount | Number of blocks |
| 11 | blockSizes | Comma-separated block lengths |
| 12 | blockStarts | Comma-separated block offsets from chromStart |

A BED3 file has coordinates only. A BED6 file adds a name, a score, and a strand. A BED12 file can describe a spliced feature by splitting it into blocks. In a BED12 record, blockSizes holds the length of each exon and blockStarts holds each exon's offset from chromStart, so an exon that starts at the very beginning of the feature has an offset of 0. The block count must match the number of entries in both lists. Exons must be sorted and must not overlap.

Coordinates are 0-based and half-open. The first base of a chromosome is position 0 in BED, and chromEnd points one base past the final base of the feature. A feature that covers bases 1 through 100 in 1-based counting is written as start 0 and end 100 in BED. Both describe 100 bases. The half-open convention is why two adjacent intervals, [1000, 1100) and [1100, 1200), can touch without overlapping.

BED files may contain comment lines starting with #, plus optional `track` and `browser` lines that control display in the UCSC browser. Those lines are not data and must be stripped before many command-line tools will read the file.

The format has no field for a gene identifier, a transcript identifier, a feature type, a reading frame, or a parent feature. Everything about biology lives in the free-text name column, which has no uniqueness requirement and no controlled vocabulary. That is a feature for interval work and a limitation for annotation work. Two BED files can share coordinates and mean completely different things.

Interval-oriented tools read BED natively. PanDepth accepts a BED file as one of its interval inputs alongside alignments, which is exactly the use case BED was built for [1].

### GFF3: a hierarchy of features

GFF stands for General Feature Format. Version 3 is the current specification, maintained through the Sequence Ontology project, and it is the version meant when someone writes GFF3 without qualification. Older GFF2 files exist and use a different attribute syntax, so always check the first line of a file called `.gff`.

GFF3 has exactly nine tab-separated columns.

| Column | Name | Notes |
|--|--|--|
| 1 | seqid | Reference sequence name |
| 2 | source | Tool or database that produced the feature |
| 3 | type | Sequence Ontology term such as gene, mRNA, exon, or CDS |
| 4 | start | 1-based, inclusive |
| 5 | end | 1-based, inclusive |
| 6 | score | Floating-point value or . |
| 7 | strand | +, -, ., or ? |
| 8 | phase | 0, 1, or 2 for CDS features, . otherwise |
| 9 | attributes | tag=value pairs separated by semicolons |

Coordinates are 1-based and closed, so both start and end refer to real bases. A feature from base 1001 to base 1100 has start 1001, end 1100, and a length of 1100 minus 1001 plus 1, which is 100 bases.

The type column uses Sequence Ontology terms. That controlled vocabulary is what makes GFF3 portable across species and databases. A gene is `gene`, a messenger RNA is `mRNA`, an exon is `exon`, a coding sequence is `CDS`, and untranslated regions are `five_prime_UTR` and `three_prime_UTR`.

The attributes column carries the hierarchy. Every feature that other features will point to needs a unique `ID`. A child feature points to its parent with `Parent`. A gene might have `ID=gene:ENSG00000141510`, its transcript might have `ID=transcript:ENST00000311936` and `Parent=gene:ENSG00000141510`, and an exon might have `ID=exon:ENSE00003820540` and `Parent=transcript:ENST00000311936`. Because `Parent` accepts a comma-separated list, a single CDS can belong to two transcripts at once. That is a genuine advantage over GTF.

Reserved characters inside attribute values, including commas, semicolons, equals signs, tabs, and newlines, must be percent-encoded. A GFF3 file begins with the directive `##gff-version 3` on its first line. Other directives declare sequence regions or append an embedded FASTA block. That embedded block is convenient for distribution and dangerous for naive parsers, which may read sequence lines as features.

PanDepth accepts GFF interval files in the same way it accepts BED and GTF, which reflects how often GFF is used as a container for regions as well as for full gene models [1].

### GTF: a transcript-centric table

GTF stands for Gene Transfer Format, and it is also called GTF2 because it descends from GFF2. It keeps the same nine-column layout as GFF3 but changes what columns 3 and 9 mean.

| Column | Name | Notes |
|--|--|--|
| 1 | seqname | Reference sequence name |
| 2 | source | Tool or database that produced the feature |
| 3 | feature | Free-text feature name such as gene, transcript, exon, or CDS |
| 4 | start | 1-based, inclusive |
| 5 | end | 1-based, inclusive |
| 6 | score | Floating-point value or . |
| 7 | strand | + or - |
| 8 | frame | 0, 1, 2, or . |
| 9 | attributes | key "value" pairs separated by semicolons |

Coordinates are 1-based and closed, identical to GFF3. The difference is in the attributes. GTF uses space-separated key and value pairs with the value wrapped in double quotes, as in `gene_id "ENSG00000141510"` and `transcript_id "ENST00000311936"`. Every line must carry both `gene_id` and `transcript_id`, even on gene-level rows where the transcript field repeats the gene or is otherwise dummy. Common additional attributes include `exon_number`, `exon_id`, `gene_name`, `gene_biotype`, `transcript_biotype`, and `protein_id`.

The hierarchy is implicit. There is no `ID` and no `Parent`. A row belongs to a transcript because it shares the same `transcript_id`, and a transcript belongs to a gene because it shares the same `gene_id`. That model is simple and it works well for linear transcript structures. It breaks down when one feature belongs to multiple transcripts. GTF handles that case by duplicating the row once per transcript, with identical coordinates but different `transcript_id` values. A script that merges rows by coordinate and sums them will double-count.

Feature naming is not standardized across sources. GENCODE writes `UTR` for untranslated regions. Ensembl writes `five_prime_utr` and `three_prime_utr`. Older files sometimes use `5UTR`. Tools that filter on column 3 by exact string matching will silently return nothing if the expected name is absent. This inconsistency is the most common reason a GTF-based counting run produces zero or near-zero counts on one feature class.

GTF is the native output of GENCODE and one of the two annotation formats distributed by Ensembl. It is also the input format expected by the major RNA-seq quantifiers. PanDepth accepts GTF intervals as a third option for coverage over annotated features [1].

## Side-by-Side Comparison

The table below is the decision core of this article. Read across the rows that matter for your task rather than trying to absorb every cell.

| Property | BED | GFF3 | GTF |
|--|--|--|--|
| Full name | Browser Extensible Data | General Feature Format, version 3 | Gene Transfer Format (GTF2) |
| Coordinate system | 0-based, half-open | 1-based, closed | 1-based, closed |
| Column count | 3 to 12 | 9 | 9 |
| Feature type field | None | Column 3, Sequence Ontology terms | Column 3, free-text names |
| Hierarchy encoding | None | ID and Parent attributes | Implicit through gene_id and transcript_id |
| Multiple parents | Not supported | Supported with comma-separated Parent | Not supported, rows are duplicated |
| Exon structure | Blocks in BED12 | Exon features with Parent | Exon features with transcript_id and exon_number |
| Attribute syntax | Free-text name field only | tag=value pairs | key "value" pairs |
| Phase or frame | Absent | Column 8 | Column 8 |
| Strand | Column 6, optional | Column 7 | Column 7 |
| Indexed form | bgzip plus tabix, or bigBed | bgzip plus tabix | bgzip plus tabix |
| Common producers | UCSC Table Browser, MACS2, bedtools, deepTools | Ensembl, NCBI, MAKER, BRAKER | GENCODE, Ensembl, StringTie, Cufflinks |
| Common consumers | bedtools, IGV, UCSC browser, GATK -L | Ensembl VEP, SnpEff, IGV, genome browsers | featureCounts, HTSeq, STAR, StringTie, RSEM, Cell Ranger |
| Relative file size | Smallest | Large | Large |
| Best for | Interval arithmetic, coverage, peaks, blacklists | Formal feature models and cross-species annotation | RNA-seq quantification and transcript models |

The size row deserves a caveat. A BED file built from a human gene set is much smaller than the corresponding GTF or GFF3 because it typically stores one row per gene rather than one row per exon, transcript, and CDS. The difference is a consequence of how much structure each format retains, not a sign that one format is better compressed.

## The Coordinate Trap: 0-Based Half-Open vs 1-Based Closed

This is the single most consequential difference between BED and the other two formats, and it is the source of most off-by-one bugs in genomic pipelines.

Take a gene that occupies bases 1001 through 1100 on chromosome 1 in 1-based counting. In GTF or GFF3, that gene is written with start 1001 and end 1100. In BED, the same gene is written with start 1000 and end 1100. The start is shifted down by one and the end is unchanged. The length comes out to 100 in both cases.

The conversion rule is short: BED start equals GTF start minus 1, and BED end equals GTF end. Going the other direction, GTF start equals BED start plus 1, and GTF end equals BED end.

Now consider two adjacent exons. In GTF, exon one is 1001 to 1100 and exon two is 1101 to 1200. Convert them to BED without the minus-one adjustment and you get [1001, 1100) and [1101, 1200). Those intervals no longer touch, because [1001, 1100) ends at 1099 and [1101, 1200) begins at 1101, leaving base 1100 uncovered. A merge operation now produces two features where there should be one, and a coverage calculation reports a one-base gap that does not exist in the annotation. Convert correctly and you get [1000, 1100) and [1100, 1200), which touch at 1100 and merge into a single block.

The error is small enough to survive quality control. A one-base shift changes no obvious summary statistic, but it can change a codon and therefore an amino acid call in a variant annotation. It can also drop the first base of every interval in a target-capture panel, which matters when that base is the variant site.

The second coordinate trap is chromosome naming. The same human chromosome can appear as `1`, `chr1`, or `NC_000001.11` depending on the source. Tools that match intervals by sequence name will report zero overlaps when the two files disagree, and they will not warn you. Before any interval operation, compare the distinct values in column 1 of both files. If they disagree, fix the naming on one side and document which convention the project uses. The FASTA index file created by `samtools faidx` defines the reference names and their order, and every other file in the project should match it.

## Criteria That Decide Real Choices

With the formats described, the remaining question is which one to hand to a specific tool on a specific day. Six criteria cover almost every real decision.

### What the downstream tool actually parses

Check the tool's argument list, not the file extension. featureCounts expects GTF by default and switches with its `-F` option. STAR takes a GTF through `-sjdbGTFfile` when it builds its index. StringTie takes a reference GTF through `-G` and writes GTF. HTSeq-count accepts GTF or GFF. bedtools reads BED and also accepts GFF and VCF for its overlap operations. IGV displays all three. GATK accepts BED through its `-L` interval option and converts it internally to its own interval list representation.

Extensions lie. A file named `.gff` may contain GTF, and a file named `.gtf` may contain GFF3. Open the first data line and look at column 9. If values are wrapped in double quotes after a space, it is GTF or GFF2. If values use equals signs, it is GFF3. If column 9 is a bare label or absent, it is BED.

### Whether you need a hierarchy

If a single downstream step needs gene-level, transcript-level, and exon-level information from one file, use GTF or GFF3. If the only question is overlap, distance, or depth over a region, BED is lighter and simpler.

A practical test: can the analysis be expressed entirely as "which intervals intersect which other intervals". If yes, BED. If it needs a phrase like "the coding sequence of this transcript" or "the exons shared between these two isoforms", it needs a hierarchy, and BED cannot supply it.

### Multi-parent features and alternative splicing

GFF3 represents a CDS shared by two transcripts as one row with a comma-separated `Parent`. GTF represents the same biology as two rows with identical coordinates and different `transcript_id` values.

Both are valid. The GFF3 version is more compact and easier to query when you want the set of transcripts that share a coding region. The GTF version is easier for per-transcript tools that read one row at a time. Problems appear when a naive script merges by coordinate and sums a column. On a GTF with shared CDS rows, that script double-counts. On a GFF3 with multi-parent rows, it undercounts transcripts because it sees one feature where there are two.

### Round-trip fidelity

GTF to GFF3 conversion is usually clean if the converter assigns unique IDs and maps `gene_id` and `transcript_id` to `ID` and `Parent`. GFF3 to GTF loses multi-parent relationships, non-standard feature types, and any attribute that does not map to a GTF key. BED to GTF is not really a conversion at all, because BED has no gene or transcript identifiers to populate the mandatory GTF attributes. Any such step requires an external mapping table.

Keep the original file. Convert on the way into each tool rather than maintaining several "canonical" copies that drift apart.

### File size, sorting, and random access

All three formats can be compressed with bgzip and indexed with tabix. The bedTools ecosystem and the samtools ecosystem both rely on this pattern. A tabix index requires the file to be sorted by chromosome and then by start position, and the chromosome order must match the order in the reference index.

Sorting a BED file with a lexical sort puts position 100 before position 20, which breaks merging and breaks indexing. Always sort numerically on the start column: `sort -k1,1 -k2,2n`. Sorting a GTF or GFF3 follows the same rule, with the numeric key on column 4.

### When BED is the right answer

BED is the right answer in four recurring situations. The first is coverage and depth over a custom region set, where the regions have no biological identity beyond their coordinates and the result is a per-region number [1]. The second is peak data from ChIP-seq or ATAC-seq, where MACS2 and similar callers emit BED or BED-like peak files. The third is exclusion sets and blacklists, which are pure intervals by design. The fourth is display in the UCSC Genome Browser, where BED is the native custom track format.

In each case the question is spatial and the answer is a number or an overlap. No gene model is needed.

## Worked Scenarios

The following scenarios show how the choice plays out in real pipelines. Commands are illustrative and assume the standard tools are installed.

### Counting RNA-seq reads per gene

The standard path starts with a GENCODE or Ensembl GTF and featureCounts.

```
featureCounts -T 8 -p -a gencode.annotation.gtf -F GTF -t exon -g gene_id -o counts.txt aligned.bam
```

The `-t exon` argument tells featureCounts to assign reads to exon features. The `-g gene_id` argument tells it to aggregate exons up to genes using the `gene_id` attribute. The `-p` flag declares paired-end data. Reads that overlap more than one gene are not counted by default, which is the conservative behavior most differential expression workflows want.

If the only annotation available is a BED file, this path is blocked. featureCounts does not take BED directly. The alternatives are `bedtools multicov` against a BED of merged exons or a dedicated counting tool that accepts BED. That extra step is a sign that the project dropped structure it actually needed.

### Coverage over a custom panel

A targeted sequencing panel is a list of regions with coordinates and nothing else. BED is the natural container. PanDepth accepts BED, GTF, or GFF interval files together with BAM or CRAM alignments, and it also accepts a fixed window size when no interval file exists [1]. Its authors report faster runtimes and lower memory use than other coverage tools for both BAM and CRAM inputs, independent of read length, and it adds GC content statistics when a reference genome is supplied [1].

A bedtools alternative for simple per-region counts looks like this.

```
bedtools coverage -a targets.bed -b aligned.bam -counts > per_region_counts.tsv
```

The `-a` file is the region set and the `-b` file is the alignment. The output has one row per target with a count column appended. The BED coordinates must be 0-based half-open and sorted, and the BAM must be coordinate-sorted and indexed.

### Peak calls and blacklists

MACS2 writes BED and BED-like peak files. The coordinates are a set of genomic intervals with scores and no gene model. Downstream annotation is a separate join step against a gene annotation, not a property of the peak file.

```
bedtools intersect -a peaks.bed -b genes.bed -wa -wb > peaks_with_genes.tsv
```

The `-wa` and `-wb` flags report the original peak record and the overlapping gene record on the same line. The gene file can be a BED built from a GTF, or the annotation can be done in R with a TxDb object. Either way, the join is explicit.

Blacklists follow the same logic. An exclusion set is pure intervals. Merge overlapping entries before use.

```
sort -k1,1 -k2,2n blacklist.bed | bedtools merge -i - > blacklist_merged.bed
```

The merge step requires sorted input, which is why the sort comes first.

### Variant effect prediction

Variant consequence calling needs transcript structure, coding phase, and untranslated region boundaries. A BED file of variant positions supplies none of that. The variant file itself should be VCF, and the annotation should be GTF or GFF3.

Ensembl VEP accepts GTF-based custom annotations, and SnpEff builds custom databases from GFF3 or GTF. Both need the exon and CDS structure to decide whether a variant is synonymous, missense, or in an untranslated region. Feeding a BED file to either tool produces coordinates without consequences.

### Genome browser tracks

UCSC custom tracks are native BED. For a browser track of a few thousand regions, a plain BED file is enough. For millions of intervals, convert to bigBed, which is the indexed binary form. IGV reads BED, GFF3, and GTF. Sorting and indexing improve load times for all three.

A compact display-ready BED6 file can be produced from a sorted interval list with a name, a score, and a strand. Keep the score column in mind. The BED specification caps it at 1000, so raw read depth placed in that column saturates at 1000 and stops being quantitative. Use a bedGraph or bigWig for continuous signal and keep BED for discrete features.

### Custom annotation for a non-model organism

Annotation pipelines such as MAKER and BRAKER commonly emit GFF3 or GTF. The primary file should stay in whichever format the pipeline produced. Convert to GTF for featureCounts, and convert to BED for interval work, with a script kept under version control. Converting back and forth without a source of truth produces files that disagree in small ways, and the disagreements are hard to trace.

## How Annotation Formats Relate to FASTA, FASTQ, and Alignments

Annotation formats are one piece of a larger file format ecosystem, and mixing up their roles causes as many problems as mixing up their coordinates. The table below maps common bioinformatics file formats to the question each one answers.

| Format | What it holds | Question it answers |
|--|--|--|
| FASTA | Sequence only | What is the reference sequence |
| FASTQ | Sequence plus per-base quality | What was the raw read and how reliable is each base |
| SAM, BAM, CRAM | Aligned reads | Where did each read align |
| VCF | Variants | What variant is present at this position |
| BED, GFF3, GTF | Features and intervals | Where are the regions and what are they |
| bedGraph, bigWig | Continuous signal | What is the value along the genome |

### FASTA vs FASTQ

FASTA and FASTQ are the two sequence formats, and they differ in one decisive way. FASTA carries sequence with no quality information. FASTQ carries sequence plus a per-base quality score for every base.

A FASTA record starts with a `>` header line followed by sequence lines. A FASTQ record has four lines: an `@` header, the sequence, a `+` separator, and a quality string of the same length as the sequence. Each quality character encodes a Phred score, typically offset by 33, which represents the probability that the corresponding base call is wrong.

Sequencers write FASTQ. Reference genomes are distributed as FASTA. You can convert FASTQ to FASTA by dropping the third and fourth lines of every record, and you cannot go the other way because quality scores cannot be reconstructed. A reference FASTA and its index define the coordinate system that BED, GFF, and GTF files are written against, so the sequence names in the annotation must match the FASTA headers exactly.

### Alignments, variants, and signal

SAM, BAM, and CRAM hold aligned reads. BAM is the compressed binary form of SAM, and CRAM is a reference-based compressed form that needs the same reference FASTA used to create it. VCF holds variants. bigWig and bedGraph hold continuous signal such as coverage or fold enrichment. None of these are annotation formats, but all of them interact with annotation formats during analysis.

The practical consequence is that a complete genomics project usually contains all of these file types at once. A reference FASTA, its `.fai` index, a GTF or GFF3 annotation, FASTQ reads, BAM alignments, a VCF of variants, and BED files for intervals. Keeping the coordinate conventions straight across the set is the job of the pipeline, not of any single tool.

## Converting Between Formats Safely

Conversions are where small errors become permanent. The recipes below cover the common cases.

### Sorting and indexing

Sort a BED file numerically by chromosome and start.

```
sort -k1,1 -k2,2n input.bed > sorted.bed
```

Compress and index a BED.

```
bgzip sorted.bed
tabix -p bed sorted.bed.gz
```

Compress and index a GTF.

```
bgzip annotation.gtf
tabix -p gff annotation.gtf.gz
```

The `gff` preset works for GTF because the column layout is identical to GFF, with sequence name in column 1, start in column 4, and end in column 5.

### GTF to BED

A common need is a BED file of genes with one row per gene. This awk one-liner extracts gene rows, converts the start coordinate, and pulls the gene ID out of the attribute column.

```
awk 'BEGIN{OFS="\t"}
     $3=="gene" {
       split($9, a, "\"")
       print $1, $4-1, $5, a, 0, $7
     }' annotation.gtf > genes.bed
```

The `split` call assumes `gene_id` is the first attribute on the line, which is true for GENCODE and Ensembl GTF files. Check the first gene line before trusting it. For exon-level BED12 output, use a dedicated converter instead of awk. gffread can write BED and GFF3 from a GTF input, and the UCSC utilities `gtfToGenePred` and `genePredToBed` handle the BED12 case.

### BED to GFF3

A minimal conversion for BED6 input was never going to be lossless, because BED has no gene or transcript identifiers to populate. This awk command generates a valid GFF3 skeleton with unique IDs based on coordinates.

```
awk 'BEGIN{OFS="\t"}
     BEGIN{print "##gff-version 3"}
     NF==6 {print $1, "bed", "region", $2+1, $3, ".", $6, ".", "ID=" $1 ":" $2 "-" $3}' regions.bed > regions.gff3
```

Two details matter. The start coordinate gains one because GFF3 is 1-based. The ID is built from the coordinates, which guarantees uniqueness only if the input has no duplicate intervals. awk does not percent-encode reserved characters, so avoid commas and semicolons in any generated identifier.

### Preflight checks

Run these checks before any conversion is trusted.

1. Confirm every line has the expected number of fields with `awk -F'\t' '{print NF}' file | sort -u`.
2. Confirm chromosome names match the reference index by comparing the distinct values in column 1 against `cut -f1 genome.fa.fai`.
3. Confirm no BED interval has a negative start or a start greater than or equal to its end.
4. Confirm no GFF or GTF feature has a start less than 1 or a start greater than its end.
5. Confirm a GFF3 file starts with `##gff-version 3` and that all `ID` values are unique.
6. Confirm strand values are limited to `+`, `-`, and `.` where a strand column exists.
7. Confirm the file is sorted before indexing, and that the sort is numeric.

These checks take seconds and catch the errors that survive visual inspection.

## Common Mistakes and Limitations

The errors below account for most of the annotation bugs seen in real workflows. Each one is specific and each one has a fix.

1. Trusting the file extension. A `.gff` file may contain GTF. A `.gtf` file may contain GFF3. Inspect column 9 before choosing a parser.

2. Mixing coordinate systems. A BED built from a GTF without subtracting one from the start shifts every feature by a base toward the end of the chromosome. The shift is invisible in summary statistics and visible in variant calls.

3. Chromosome name mismatch. `chr1` and `1` and `NC_000001.11` are three names for the same sequence. An intersection between files that use different conventions returns zero rows without an error.

4. Lexical sorting. Sorting a BED with a plain string sort puts 100 before 20. Merging and indexing then behave incorrectly, and the failure is intermittent because it depends on which positions happen to cluster.

5. Treating the BED name column as a stable identifier. BED allows duplicate names and enforces no uniqueness. Joins on that column can silently fan out.

6. Using GTF where multi-parent structure matters. A CDS shared by two transcripts appears as two identical-coordinate rows. A script that merges by coordinate and sums double-counts.

7. Assuming a GFF3 to GTF conversion is reversible. It is not. Multi-parent relationships, non-standard feature types, and arbitrary attributes do not survive the round trip.

8. Passing a GTF to a script that assumes BED columns. A BED-reading script takes column 2 as the start, which in a GTF is the source string. The failure is usually an error, but a tolerant parser may silently misassign coordinates.

9. Filtering on the wrong feature name. featureCounts with `-t exon` counts nothing if the annotation uses a different name for the same concept. Check the distinct values in column 3 before setting the filter.

10. Forgetting that GTF attribute values are quoted. A naive split on the semicolon leaves the quotes attached, so `gene_id` becomes `"ENSG00000141510"` and every downstream join fails.

11. Putting raw depth in the BED score column. The specification caps the score at 1000, so a deep region saturates and stops being quantitative. Use bedGraph or bigWig for signal.

12. Leaving `track` and `browser` lines in a BED file. Strip them with `grep -v '^track\|^browser'` before running command-line tools.

13. Leaving an embedded `##FASTA` block in a GFF3 file. Strip everything from the directive onward before interval operations, or use a parser that understands the block.

14. Losing the assembly with the file. None of the three formats records which reference assembly it was built against. Store the assembly name, the reference FASTA checksum, and the chromosome naming convention alongside the annotation.

The broader limitation is that all three formats describe coordinates on a reference that must be versioned and shared. Annotation files from different releases of the same database are not interchangeable even when the file names look the same. Record the release number, the assembly, and the source URL in the project metadata. Individual projects may need specific handling beyond these general rules.

## Frequently Asked Questions

### Is BED better than GFF or GTF?

No. BED is better only for pure interval work such as coverage, overlap, and peak handling. It has no field for a gene ID, a transcript ID, a feature type, or a parent relationship. GFF3 and GTF carry that structure, which is why annotation-aware tools ask for them.

### Can I use a GTF file where a tool asks for GFF3?

Sometimes, but treat it as a conversion problem rather than a compatibility shortcut. GTF and GFF3 share the same nine-column layout, so a parser may read the coordinates correctly and then fail on attributes. Convert with gffread or AGAT and validate the output before running the tool.

### Why is my BED file one base off compared with my GTF?

Because the two formats count from different origins. BED starts at 0 and excludes the end coordinate. GTF and GFF3 start at 1 and include both ends. To convert a GTF feature to BED, subtract 1 from the start and keep the end unchanged.

### Does BED support gene names?

Only as free text in the name column. BED has no enforced gene identifier, no uniqueness rule, and no link to a transcript. A name like TP53 in column 4 is a label, not a queryable annotation.

### What is the difference between FASTA and FASTQ?

FASTQ stores a per-base quality score for every read. FASTA stores sequence only. Sequencers write FASTQ, reference genomes are distributed as FASTA, and you cannot reconstruct FASTQ qualities from a FASTA file.

### Which annotation format should I use for a new project?

Keep the primary annotation in the format your source provides, which is usually GTF from GENCODE or Ensembl and GFF3 from NCBI or a [genome annotation pipeline](/blog/guides/genome-annotation-pipeline). Derive BED files for interval work and keep the conversion script under version control.

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "FAQPage",
  "mainEntity": [
    {
      "@type": "Question",
      "name": "Is BED better than GFF or GTF?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "No. BED is better only for pure interval work such as coverage, overlap, and peak handling. It has no field for a gene ID, a transcript ID, a feature type, or a parent relationship. GFF3 and GTF carry that structure, which is why annotation-aware tools ask for them."
      }
    },
    {
      "@type": "Question",
      "name": "Can I use a GTF file where a tool asks for GFF3?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Sometimes, but treat it as a conversion problem rather than a compatibility shortcut. GTF and GFF3 share the same nine-column layout, so a parser may read the coordinates correctly and then fail on attributes. Convert with gffread or AGAT and validate the output before running the tool."
      }
    },
    {
      "@type": "Question",
      "name": "Why is my BED file one base off compared with my GTF?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Because the two formats count from different origins. BED starts at 0 and excludes the end coordinate. GTF and GFF3 start at 1 and include both ends. To convert a GTF feature to BED, subtract 1 from the start and keep the end unchanged."
      }
    },
    {
      "@type": "Question",
      "name": "Does BED support gene names?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Only as free text in the name column. BED has no enforced gene identifier, no uniqueness rule, and no link to a transcript. A name like TP53 in column 4 is a label, not a queryable annotation."
      }
    },
    {
      "@type": "Question",
      "name": "What is the difference between FASTA and FASTQ?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "FASTQ stores a per-base quality score for every read. FASTA stores sequence only. Sequencers write FASTQ, reference genomes are distributed as FASTA, and you cannot reconstruct FASTQ qualities from a FASTA file."
      }
    },
    {
      "@type": "Question",
      "name": "Which annotation format should I use for a new project?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Keep the primary annotation in the format

## Sources

1. [PanDepth, an ultrafast and efficient genomic tool for coverage calculation.](https://pubmed.ncbi.nlm.nih.gov/38701418/)