Why Git Stores File History Backwards

Quick Answer / TL;DR: Git optimizes repository performance by storing the newest version of your files whole while keeping older history as backward deltas. When running garbage collection, Git sorts files by size and builds older, smaller revisions out of larger, newer ones. This ensures modern checkouts are fast, requiring minimal delta reconstruction.
Checking out main in a decade-old repository with 50,000 commits should logically crawl to a halt. The fact that it happens in milliseconds comes down to a clever bit of engineering inside Git's object store: Git stores your file history backwards.
I find this design choice brilliant because it directly targets real developer behavior. We rarely care about building code from three years ago, but we check out today's branch dozens of times a day.
How does Git delta compression work?
Git compresses repository history by pairing similar files inside packfiles, keeping one complete base object and storing the other as a delta. Instead of duplicating entire file contents across commits, Git writes tiny byte-level instructions detailing how to transform one revision into another.
When I run git commit, Git doesn't immediately worry about space efficiency. It writes each modified file out as a standalone, loose object in .git/objects. This makes commits instant, but loose objects quickly bloat your disk.
Later, when I run git gc or execute a git push, Git squashes those loose objects into a single compressed binary .pack file. During this packing phase, Git searches for files with matching paths and similar content. It keeps one file completely whole as a base object and compresses the other into a delta—a lean instruction list that basically says, "copy these bytes from the base, then insert these new bytes here."
Why does Git store history backwards?
Git stores history backwards because developers almost exclusively query and checkout recent code rather than historical commits. By keeping the latest version of a file whole and storing past commits as reverse deltas, Git eliminates the need to apply historical patches when checking out current branches.
To understand why this works so well, consider how source code naturally evolves. Over time, our files mostly grow as we add features and handle new edge cases.
Let's say I have an 80-kilobyte file in my repository. I add a single line of logging, save, and commit.
| Repository Stage | File Revision | Storage Format | Disk Representation |
|---|---|---|---|
| Initial Commit | Revision 1 | Loose Object | Full ~80 KB file |
| Subsequent Commit | Revision 2 (Added 1 line) | Loose Object | Full ~80.01 KB file |
Post-git gc |
Revision 2 (Newest) | Base Packfile Object | Full ~80.01 KB file |
Post-git gc |
Revision 1 (Oldest) | Reverse Delta Object | ~11 Byte patch |
When Git garbage collects these two commits, it compares both versions. Because Revision 2 is slightly larger than Revision 1, Git sorts by size and keeps Revision 2 completely intact. It then turns Revision 1 into an 11-byte reverse delta that describes how to roll back Revision 2 to reproduce Revision 1.
If I delete code instead, Revision 1 ends up larger than Revision 2. Git still builds the smaller file out of the larger one, choosing whichever base object minimizes total packfile size.
How does Git choose which file version to keep whole?
Git sorts candidate files by size and type, placing the largest file first as the base object. Since files typically grow over time, the newest revision is usually the largest, making it the base while older, smaller commits become reverse deltas.
Whenever I want to see this mechanism in action on a repository, I use Git's plumbing tools to inspect the packfile index directly:
git verify-pack -v .git/objects/pack/pack-*.idx
This command lists object SHA-1 hashes, uncompressed sizes, and delta chains. When I review this output, current HEAD blobs appear as standalone objects, while older commits link back to base blobs through explicit delta offsets.
Because checking out today's code only requires reading whole base files, my dev workflow stays fast. It is last year's code that gets rebuilt from reverse deltas—and I almost never need to build last year's code.
Frequently Asked Questions
What happens to Git packfile storage when you delete code?
When you delete code, the newer file version becomes smaller than the older version. Because Git prefers using larger files as base objects to construct smaller deltas, Git may store the older revision as the base object and the newer revision as a delta if it saves disk space.
When does Git convert loose objects into delta packfiles?
Git automatically converts loose objects into packed deltas during routine maintenance routines like git gc, during automatic auto-packing triggers, or when transferring objects over the network during git push or git fetch operations.
Why is git gc necessary for Git performance?
git gc cleans up unreachable commits, consolidates loose object files into packed archives, and calculates optimal delta compression chains. Without garbage collection, repositories grow bloated with redundant full-file objects, slowing down checkout and diff operations.
Comments
Join the discussionNo comments yet. Be the first to comment.



