Packfiles, gc, and routine maintenance
Loose objects — one file per blob, tree, commit, tag — are simple but expensive at scale. A repo with 100k commits and millions of blobs would have millions of files in .git/objects/. Filesystem performance, listing speed, and remote-transfer overhead all collapse. Git's answer is packfiles: many objects compressed and concatenated into one .pack file with a companion .idx index. Operations on packed objects are fast; transfer is dramatically smaller.
git gc ("garbage collect") packs loose objects, removes unreachable ones, and rebuilds indexes. Modern Git runs gc --auto automatically when thresholds are crossed (200 loose objects, 7000 too-many-packs, etc.). For most repos that is enough. For long-lived shared repos, periodic git gc --aggressive reorganizes packs more thoroughly at the cost of more CPU; do this on a server-side mirror or off-hours, not in the middle of your workday.
"Unreachable" is the key concept. Any object reachable from at least one ref (HEAD, branch, tag, reflog entry, stash) survives. Objects no other ref can reach become candidates for GC. The reflog acts as a 90-day safety net — even orphaned commits remain reachable via reflog entries until their TTL expires. git gc --prune=now bypasses the safety net and immediately removes unreachable objects, useful when you really want sensitive content gone (paired with git filter-repo for history rewrite).
Routine maintenance commands. git fsck verifies object integrity. git count-objects -v shows loose vs packed counts and on-disk size. git maintenance start (Git 2.30+) registers background tasks (gc, pack-refs, commit-graph, prefetch) so the repo stays optimized without explicit cron. Modern Git also writes a commit-graph file that accelerates ancestry queries (git log --graph, git merge-base); enable with git config core.commitGraph true and refresh with git commit-graph write.