All chapters in this track
- 01Chapter 1: Why Coding Standards Matter: Readability, Onboarding, Defects, Consistency at Scale
- 02Chapter 2: Naming Conventions
- 03Chapter 3: Formatting and Style
- 04Chapter 4: Comments and Self-Documenting Code
- 05Chapter 5: Error Handling Conventions
- 06Chapter 6: Code and Project Organization (Files, Modules, Packages)
- 07Chapter 7: Writing Testable Code
- 08Chapter 8: Commit Hygiene: Atomic Commits, Messages, PR-Sized Changes
- 09Chapter 9: Language-Specific Idioms and Anti-Patterns Across TypeScript and Python
- 10Chapter 10: Security-Conscious Basics: Input Validation, Secrets Handling, Injection Risks
- 11Chapter 11: Enforcing Standards Automatically: Linters, Formatters, CI Gates
- 12Chapter 12: Creating, Documenting, and Evolving a Team Style Guide (Exceptions and Legacy Code)
- 13Chapter 13: Worked Example: Refactoring a Messy Function to Standard
- 14Chapter 14: Worked Example: Drafting a Style Guide Section From Scratch
- 15Chapter 15: Common Interview Questions About Coding Standards and Code Quality
Chapter 8 of 15
Chapter 8: Commit Hygiene: Atomic Commits, Messages, PR-Sized Changes
Why this matters
A commit history is a form of documentation most engineers underinvest in, despite consulting it constantly — git blame, git log, and git bisect are only as useful as the commit history they're reading, and a history of vague, oversized, tangled commits actively defeats every one of these tools. Commit hygiene is a coding standard the same way naming or formatting is: a small, consistent discipline that compounds into a large difference in how debuggable and navigable a codebase's history is.
Atomic commits: one logical change per commit
[BEGINNER] An atomic commit contains exactly one logical, self-contained change — not necessarily one line or one file, but one coherent unit of work that could be described in a single clear sentence, and that could, in principle, be reverted independently without unintentionally reverting unrelated work.
BAD (tangled): one commit containing an unrelated bug fix, a new feature,
and a formatting cleanup across the whole file, all mixed together --
reverting this commit for ANY one reason reverts ALL three changes.
GOOD (atomic): three separate commits:
1. "Fix null pointer exception in getUserPreferences"
2. "Add dark mode toggle to settings page"
3. "Reformat settings.ts per updated Prettier config"
Each can be reviewed, reverted, or cherry-picked independently, with zero
risk of accidentally undoing unrelated work.
[SENIOR INSIGHT] The real payoff of atomic commits shows up specifically during git bisect (binary-searching commit history to find which commit introduced a bug) and git revert (undoing a specific change) — both tools work by assuming each commit represents one coherent, independently-revertible unit. A tangled commit containing three unrelated changes makes git bisect identify "the commit" that introduced a regression, without being able to tell which of the three changes within it was actually responsible — defeating the tool's entire value proposition.
Commit messages: the "why," not just the "what"
[BEGINNER] Directly paralleling Chapter 4's comment guidance: a commit's diff already shows what changed; a commit message should explain why — information the diff alone cannot convey.
BAD: "fix bug"
BAD: "update code"
BAD: "changes per review"
GOOD:
Fix race condition in payment retry logic
Concurrent retry attempts for the same order could both pass the
"already processed" check before either updated the status, causing
duplicate charges under load. Added a row-level lock (SELECT FOR UPDATE)
around the check-and-update sequence.
Fixes INC-4521.
A commit message's first line should be a concise summary (conventionally kept short — many teams enforce 50-72 characters — since it's what shows up in git log --oneline and many tooling UIs); the body, when needed, explains the reasoning, trade-offs considered, or context a future reader (including future-you) would need to understand why this change was made, not just what it touched.
[IN PRACTICE] Many teams standardize on a structured format like Conventional Commits (feat:, fix:, refactor:, docs:, etc.) specifically because it makes commit history machine-parseable — enabling automated changelog generation and semantic version bumping directly from commit history, an automation payoff (Chapter 11's theme, applied to commit messages specifically) beyond just human readability.
feat(checkout): add support for saved payment methods
fix(auth): resolve token refresh race condition
refactor(orders): extract shipping calculation into separate module
PR-sized changes: keeping review effective
[SENIOR INSIGHT] A pull request's size has a measurable, well-documented effect on review quality: review thoroughness drops sharply as PR size grows, not linearly but in a way that makes very large PRs receive proportionally less careful scrutiny per line than small ones — a reviewer facing a 2,000-line diff can realistically only skim, while a reviewer facing a 150-line diff can genuinely reason about every line's correctness and design implications. This isn't a matter of reviewer diligence; it's a well-established, near-universal pattern in how humans process large amounts of unfamiliar information under time pressure.
Practical guidance for keeping PRs reviewable:
- Split a large feature into a sequence of smaller, independently-reviewable PRs where the underlying work allows it (e.g., "add the data model," then "add the service layer," then "add the API endpoint," rather than one PR containing all three).
- When a large PR is genuinely unavoidable (a large, atomic refactor that can't be meaningfully split), say so explicitly in the PR description, and consider a synchronous walkthrough with reviewers rather than expecting equally careful async review of a huge diff.
- Separate pure formatting/reformatting changes into their own PR, distinct from logic changes — mixing them makes it much harder for a reviewer to distinguish "this line changed because of reformatting" from "this line changed because the logic changed," which is exactly Chapter 3's formatting-automation principle applied at the PR-review level.
Common mistakes
- Bundling unrelated changes into one commit or one PR, defeating
git bisect/git revert's value and forcing reviewers to evaluate multiple unrelated concerns simultaneously. - Writing commit messages that restate the diff ("changed file X") rather than explaining the reasoning behind the change — exactly Chapter 4's "explain why, not what" principle, applied to commit messages.
- Submitting very large PRs and expecting the same review thoroughness a small PR would receive — large PRs measurably receive shallower per-line scrutiny, regardless of reviewer diligence or intention.
- Mixing formatting/reformatting changes with logic changes in the same commit or PR, obscuring which lines changed for which reason and making the diff much harder to review carefully.
Checklist: maintaining good commit hygiene
- Before committing, ask "is this one coherent, independently-revertible logical change?" If it's actually several unrelated changes, split it into separate commits.
- Write commit messages that explain WHY a change was made, not just a restatement of what files changed — treat this exactly like Chapter 4's comment guidance.
- Keep PRs as small as the underlying work reasonably allows, splitting large features into a sequence of smaller, independently-reviewable PRs where possible.
- Never mix pure formatting changes with logic changes in the same commit/PR — keep them separate so a reviewer (or a future
git blame) can distinguish the two kinds of change immediately.
Discussion / interview questions
-
"What makes a commit 'atomic,' and why does it matter?" Strong answer: An atomic commit represents one coherent, independently-revertible logical change; it matters because tools like
git bisectandgit revertassume this property to work effectively — a tangled commit containing multiple unrelated changes defeats both tools' core value proposition. -
"What should a good commit message include, beyond a one-line summary of what changed?" Strong answer: The reasoning behind the change — why it was necessary, what alternative was considered and rejected, any relevant context (an incident reference, a business requirement) — information the diff itself can't convey, directly paralleling the "explain why, not what" comment principle.
-
"Why does a very large pull request tend to receive lower-quality review than several smaller ones covering the same total work?" Strong answer: Review thoroughness drops sharply, not linearly, as diff size grows — a reviewer facing a very large diff can realistically only skim it, while a reviewer facing a well-scoped smaller diff can genuinely reason about every line, a well-documented pattern in how humans process large amounts of unfamiliar information under time pressure.
-
"Why should formatting changes be kept in a separate commit/PR from logic changes?" Strong answer: Mixing them makes it much harder for a reviewer (or a future
git blameinvestigation) to distinguish which lines changed due to reformatting versus which changed due to an actual logic change, obscuring the real, meaningful diff inside a large amount of formatting noise.
Chapter summary / cheat sheet
- Atomic commits: one coherent, independently-revertible logical change per commit — essential for
git bisectandgit revertto work as intended. - Commit messages: explain WHY, not just WHAT — the diff already shows what changed; the message's value is the reasoning the diff can't convey.
- PR size: review thoroughness drops sharply as diff size grows — keep PRs as small as the work reasonably allows, splitting large features into a sequence of smaller ones.
- Keep formatting/reformatting changes in separate commits/PRs from logic changes, so reviewers and future history investigations can distinguish the two easily.