Skip to content

Object Model

Git stores everything as four kinds of object addressed by the hash of their content: blobs, trees, commits and tags. Interviewers ask about this because it explains snapshots, deduplication, integrity and why history is immutable, and because cat-file lets you prove it in three commands.

Track: Advanced · Interview weight: High


Must-Know Facts

Fact Value Verify with
Blob File content, no name or metadata git cat-file -p <blob>
Tree A directory: names, modes, and blob/tree SHAs git cat-file -p <tree>
Commit A tree plus parent(s), author, committer, message git cat-file -p HEAD
Tag object An annotated tag: target, tagger, message git cat-file -t <tag>
Object id Hash of type + size + content git hash-object
Content addressed Identical content, identical SHA (dedup) git ls-tree HEAD
Type of an object git cat-file -t <sha> git cat-file -t HEAD
Size of an object git cat-file -s <sha> git cat-file -s HEAD
Snapshot Each commit points at a full tree, not a diff git cat-file -p HEAD
Immutability Changing content changes the SHA, so a commit is fixed git hash-object

Four Object Types

Every piece of a repository is one of four object types, each stored once and named by its hash.

Object Holds Points to
Blob Raw file content nothing
Tree A directory listing blobs and subtrees
Commit A snapshot and metadata one tree, and parent commit(s)
Tag Annotated-tag metadata one object (usually a commit)

A blob is content with no filename; the name lives in the tree that references it. A commit ties a tree (the snapshot) to its parents (the history).


The Commit Object

git cat-file -p HEAD prints a commit's stored form: a tree line, zero or more parent lines, author and committer with timestamps, and the message.

git cat-file -p HEAD

Output:

tree a4c85bd8861b37fa8e61fea725010f6ef70a0249
author Amina Yusuf <amina@example.com> 1789876800 +0500
committer Amina Yusuf <amina@example.com> 1789876800 +0500

Initial commit

The commit itself holds no files; it references a tree that is the complete snapshot. git cat-file -t HEAD confirms the type is commit, and a first commit has no parent line.


Trees and Blobs

A tree is a directory: each entry is a mode, a type, a SHA and a name. Entries are either blobs (files) or subtrees (subdirectories).

git cat-file -p HEAD^{tree}

Output:

100644 blob a00621bb3a9b990ee018f8d04c2d3140800d6ca6    README.md
040000 tree 212c4fdae26c1d9b9ecee80ce328176151064663    src

README.md is a blob; src is a subtree, which is another tree object listing its own entries. Following a subtree reaches its files, and a blob is only the bytes.

git cat-file -p HEAD^{tree}:src

Output:

100644 blob b80e3222ab264bd7cafb376749bd18814fd66776    app.py

The mode 100644 is a normal file, 100755 an executable, and 040000 a directory. This tree-of-trees is the snapshot: reproducing it reproduces the working directory exactly.


Content Addressing

An object's id is the SHA of its type, size and content, so identical content always yields the same id. git hash-object computes it, and it matches the blob already in the tree.

printf '# Shop\n' | git hash-object --stdin

Output:

a00621bb3a9b990ee018f8d04c2d3140800d6ca6

That is the same SHA as README.md in the tree above. Because the id is the content, two identical files store one blob, referenced twice.

git ls-tree HEAD | grep -E 'README|COPY'

Output:

100644 blob a00621bb3a9b990ee018f8d04c2d3140800d6ca6    COPY.md
100644 blob a00621bb3a9b990ee018f8d04c2d3140800d6ca6    README.md

Both files point at one blob. This is why Git does not bloat when a file is copied or unchanged across commits: the blob is stored once.

Content addressing gives dedup, integrity and immutability at once

Because the id is the hash of the content, unchanged files are shared (dedup), any corruption changes the hash and is detectable (integrity), and editing a commit produces a different id rather than mutating it (immutability). All three properties fall out of the same design.


Writing an Object by Hand

Plumbing commands read and write objects directly, which shows the store has no magic. git hash-object -w writes a blob and returns its SHA; git cat-file -p reads any object back by SHA.

echo 'hello internals' | git hash-object -w --stdin
git cat-file -p 7fb30904241a9ffd036dc6695c5de5b1d977c7fc

Output:

7fb30904241a9ffd036dc6695c5de5b1d977c7fc
hello internals

The blob now exists in .git/objects with no commit or tree referencing it, a dangling object until something points at it. This is the layer git add builds on internally.

The empty tree has a constant, universal SHA

git hash-object -t tree /dev/null always yields 4b825dc642cb6eb9a060e54bf8d69288fbee4904 in every SHA-1 repository, because the empty tree's content is fixed. Tools use it as a stable base, for example diffing a first commit against the empty tree.


How It Fits Together

The objects form a directed acyclic graph. A branch ref points at a commit; the commit points at a tree and its parent commits; the tree points at blobs and subtrees.

Walking commit -> tree -> blobs reconstructs a snapshot, and walking commit -> parent -> parent walks history. Nothing points forward, and every edge is a content hash, which is why the whole graph is verifiable and why a commit's id fixes everything reachable from it.


Common Errors

fatal: Not a valid object name HEAD^{tree} in a new repository

Cause: the repository has no commits, so HEAD does not resolve to a commit or tree.

Fix: make the first commit; the object graph exists only once something is committed.

error: object file .git/objects/... is empty or a corrupt-object message

Cause: an object in the store is damaged, which content addressing detects because the hash no longer matches.

Fix: git fsck reports the bad object; recover it from another clone (its SHA is identical everywhere) or a backup.


Interview Checkpoints

L1: What are Git's object types, and what does each hold?

Say first: blob (file content), tree (a directory of names pointing at blobs and subtrees), commit (a tree plus parents and metadata), and tag (annotated-tag metadata pointing at an object).

Proof: git cat-file -t <sha> names the type; git cat-file -p HEAD shows a commit referencing a tree.

Follow-up: Where is a file's name stored, given that a blob has none?

L1: What does it mean that Git is content-addressed?

Say first: an object's id is the hash of its content, so identical content always has the same id; this gives deduplication, integrity checking and immutability.

Proof: git hash-object of a file matches its blob SHA in the tree; two identical files share one blob.

Follow-up: Why does this mean a commit cannot be edited in place?

L2: Show that a commit references a snapshot, then walk from the commit to a file's content.

Say first: print the commit, follow its tree, then a subtree, then the blob.

Proof:

git cat-file -p HEAD
git cat-file -p HEAD^{tree}
git cat-file -p HEAD^{tree}:src

Follow-up: How does Git avoid re-storing an unchanged file in the next commit?

L2: Write a blob into the object store by hand and read it back.

Say first: git hash-object -w writes it and returns the SHA; git cat-file -p <sha> reads it back.

Proof:

echo 'x' | git hash-object -w --stdin
git cat-file -p <sha>

Follow-up: What is that object called until a tree or commit references it?

L3: git fsck reports a corrupt object. How is that even detectable, and how do you recover?

Say first: the object's stored hash no longer matches its content, which content addressing makes detectable; recover the object from another clone, where its SHA is identical.

Proof: git fsck names the bad SHA; git cat-file -t on it fails; copying the object from a good clone restores it.

Follow-up: Why is the same object guaranteed to have the same SHA in every clone?

L4: Exactly what bytes does Git hash to produce a blob's SHA?

Say first: it hashes a header of the object type and content length, a NUL byte, then the raw content: blob <size>\0<content>, and the SHA of that is the object id.

Proof: printf '# Shop\n' | git hash-object --stdin reproduces the blob SHA exactly, matching the tree entry.

Don't say: "It hashes only the file content." The type-and-size header is part of the hashed bytes, which is why a blob and a tag of the same bytes differ.


Captured on macOS 26 with git 2.50.1 (throwaway local repositories), 2026-09.