- Authors

- Name
- Youngju Kim
- @fjvbn20031
- Introduction — A PR Is Communication, Not Just Code
- Small, Single-Purpose PRs
- Commit Messages: Conventional Commits
- Put the "Why" in the Body, Not the "What"
- Self-Review Before You Open It
- A Good PR Description: Context, Change, Testing
- Empathy for the Reviewer
- Splitting Big Work with Stacked PRs
- Wrapping Up
- References
Introduction — A PR Is Communication, Not Just Code
Everyone has had a PR sit for days with no review. Meanwhile, some PRs get an "LGTM" the moment they go up and merge right away. Is it a difference in coding skill? Not necessarily. PRs that merge fast have, separate from technical skill, a communication skill.
Here is the key framing. The moment you open a PR, you haven't just written code; you've sent your reviewer a request that says "please understand and approve this change." The reviewer can't read your mind. All they have is the diff and your description. So a good PR is, in the end, one that gets the reviewer to maximum confidence with minimum effort.
This post lays out practical habits for writing such PRs and commit messages. If Git commands aren't yet second nature, it's worth getting a feel for branch and commit flow first in this site's Git Playground.
Small, Single-Purpose PRs
If you name the single highest-impact habit, it's clearly keeping PRs small. Between one 1,000-line PR and ten 100-line PRs, the review quality is overwhelmingly higher for the latter.
The reason lies in human attention. When a reviewer faces a large diff, two things happen. First, focus drops and bugs slip through. Second, the size is daunting, so the review keeps getting deferred. Both research and field experience say the defect-detection rate of a review falls sharply as the change grows.
So the principle is that one PR carries one purpose.
bad PR: "user profile feature"
- add a new API endpoint
- swap out an unrelated logging library
- reformat indentation across the file
- fix 3 typos
→ reviewer: what am I supposed to look at? is this style change hiding a bug?
split into 3 good PRs:
PR 1: add profile API endpoint (feature)
PR 2: swap logging library (infra)
PR 3: style / typo cleanup (chores)
→ each can be reviewed, merged, and rolled back independently
Mix a feature change, a refactor, and formatting into one PR, and the reviewer wears out trying to tell which lines are "the real change" and which are "just moved." Commit formatting separately, and pull refactors into their own PR. That alone makes reviews noticeably faster.
Commit Messages: Conventional Commits
Commit messages have a widely-used convention too. Conventional Commits is a spec that prefixes the first line of a commit with the kind of change.
<type>(<scope>): <summary>
feat(auth): add password reset via email
fix(api): handle null user in settlement job
docs(readme): clarify local setup steps
refactor(cart): extract price calculation
test(order): add cases for partial refunds
chore(deps): bump lodash to 4.17.21
Common types are feat (feature), fix (bug fix), docs (docs), refactor (improvement with no behavior change), test (tests), and chore (build, deps, and other chores).
The benefit of this spec goes beyond mere uniformity. Machines can now read your commits. Since feat and fix are distinguishable, tools can bump versions automatically (semantic versioning) and generate a CHANGELOG. It's good for humans too. A quick scan of git log tells you at a glance what happened in the project.
The summary line (first line) has a few more conventions. Write it in the imperative ("add" not "added"), keep it around 50 characters, and drop the trailing period. It reads naturally if you think of it as filling in the blank of "Applying this commit will ___."
Put the "Why" in the Body, Not the "What"
This is where junior and mid-level diverge. A beginner's commit message records what changed. But the diff already shows that. What you actually need is why it changed.
weak message (what - already in the diff):
fix: change timeout from 30 to 60
good message (why - not in the diff):
fix(upload): raise timeout to 60s for large video uploads
When handing the original video (up to 2GB) to the encoding server
after payment, the 30s timeout was frequently exceeded and uploads
failed. Measured p95 transfer time is 48s, so we raise it to 60s
with some headroom. The real fix (chunked upload) is tracked
separately in issue #482.
The lower message is decisive for whoever reads this code six months later (usually future-you). It answers "why exactly 60s? why not 30?" ahead of time, notes that it isn't the real fix, and links the follow-up issue. Code says what it does, but only a human can record why it came to be that way. The commit body is where that record belongs.
As a rule: put one blank line after the summary, and in the body below it, describe the background, the reason, and the trade-offs. Trivial commits (a typo fix) can skip the body, but any commit that captures a decision must leave the "why."
Self-Review Before You Open It
Right before opening a PR, before sending it to anyone, the habit of reading your own diff top to bottom is surprisingly powerful. You become a reviewer of your own code.
# before opening, look over everything you changed again
git diff main...HEAD
What self-review catches is usually minor but exactly the stuff that annoys reviewers: a leftover debug print or console.log, commented-out dead code that hitched a ride, a temp file committed by accident, a typo in a commit message. When a reviewer finds these and leaves a comment, it adds a round trip and delays the merge by that much.
Self-review removes that round trip in advance. Imagine "what will the reviewer ask here?" and pre-empt the likely questions with a comment next to the code. For example, a self-comment like "this part may look odd, but the external API only responds in this shape, so there was no choice" saves the reviewer a lot of time.
A Good PR Description: Context, Change, Testing
The PR description is the first screen a reviewer sees before reading the diff. Set the direction well here and the review goes much more smoothly. I recommend structuring it around three axes.
## Context (Why)
Why this change is needed. What problem or requirement prompted it.
Link the related issue. So the reviewer understands without prior context.
## What changed
What was changed and how. For a large diff, a short guide by file/area.
If there were important design decisions, the rationale too.
## How it was tested
How you verified it. Tests added, manual steps, screenshots, etc.
The reviewer's basis for believing "this actually works."
These three axes answer, in order, the three questions a reviewer always has: "why are we doing this?", "what changed?", and "is it really working?". Don't skip the testing section in particular. For a UI change, one before/after screenshot beats a hundred words. The moment a reviewer confirms "this person verified it," they relax and lean toward approval.
Empathy for the Reviewer
The one attitude running through all these habits is empathy for the reviewer. When you open a PR, imagine a time-pressed colleague on the other side of that screen. Every action that lowers their cognitive load is what gets your PR through fast.
Concretely:
- Guide the reading order: A single line like "read
parser.pyfirst, thenmain.pywhich uses it, and it'll be easier to follow" acts as a map for the reviewer. - Pre-justify big decisions: Write out contentious choices ("why Y instead of X") in the description before the reviewer has to ask.
- Match PR size to the reviewer's time: For an urgent hotfix, smaller still. Even a big feature deserves the courtesy of being split into reviewable units.
- Don't take feedback defensively: Review comments are about the code, not about you. "Good catch, fixed it" is faster than an argument.
Review is, in the end, a thing between people. Consistently opening PRs that help the reviewer builds trust, and your next PR goes through faster.
Splitting Big Work with Stacked PRs
The principle "keep it small" and the reality "I have to build a big feature" often collide. The useful technique here is stacked PRs. You split big work into a chain of small, interdependent PRs, stacking each on top of the previous one.
main
└── PR 1: DB schema + migration (reviewed independently)
└── PR 2: repository layer on top of PR 1
└── PR 3: API endpoints on top of PR 2
└── PR 4: UI wiring on top of PR 3
Each PR uses the one just below it as its base branch. As a result, the reviewer works through four 200-line pieces in order, and each piece is understood on top of the previous context. That's far better than dumping 800 lines at once. When the front PR merges, you retarget the next PR's base to main and continue.
Stacked PRs take a little diligence to manage, because when the front PR changes you have to rebase the ones behind it. But most team tooling helps with this, and the "reviewable size" payoff easily outweighs the cost. When a feature looks like it's going to grow, designing it as a stack from the start is easier than trying to break up a giant PR later.
Wrapping Up
The secret to a fast-merging PR isn't flashy code; it's consideration for the reviewer. Small, single-purpose PRs, a history tidied with Conventional Commits, a commit body that carries the "why", a self-review of your own, a description with context, change, and testing, and stacked PRs for big work. All of it points to one thing: getting the reviewer to maximum confidence with minimum effort.
Writing the code is half the job; the other half is making that code understandable and trustworthy to someone else. If the Git workflow is still unfamiliar, try moving branches, merges, and rebases yourself in the Git Playground to build intuition. Once the tools are second nature, you can spend more of your attention on crafting good PRs.
References
- Conventional Commits specification: https://www.conventionalcommits.org/
- Google Engineering Practices, "The CL author's guide": https://google.github.io/eng-practices/review/developer/
- "How to Write a Git Commit Message" (Chris Beams): https://cbea.ms/git-commit/
- GitHub Docs, "About pull requests": https://docs.github.com/en/pull-requests