Stacked PRs: The Code Review Workflow That Changed How I Ship Features
Stacked diffs, the workflow Meta and Google ship on. (7 min)
I used to think waiting on code reviews was just part of the job.
Finish a feature, open a PR, sit on it for a day or two. Context-switch to something unrelated, or branch off the unreviewed PR and start the next piece.
That second branch was technically a stack. Just an undisciplined one.
When PR 1 came back with feedback, my “head start” on PR 2 turned into a rebase nightmare.
The first time I worked with a properly stacked PR workflow, the difference was immediate.
I was still building on top of unreviewed work, but the rebases stopped hurting, the reviewers stopped drowning, and nothing got lost between layers.
Here’s what I wish someone had walked me through.
Share this post & I’ll send you some rewards for the referrals.
The AI code reviewer that thinks like a senior engineer (Partner)
AI writes code faster than teams can review.
Codacy understands your codebase, catches real issues and intent gaps, and hands fixes to your agent.
(Thanks to Codacy for partnering on this post.)
What Stacked PRs Are (and Aren’t)
A stacked PR, also called a stacked diff, is a pull request whose base branch is another PR’s branch instead of main.
Instead of one big PR with 1,200 lines across 15 files, you break the work into a chain:
PR 1 → base:
main. Ships the data model change.PR 2 → base: PR 1’s branch. Ships the API layer.
PR 3 → base: PR 2’s branch. Ships the UI.
Each PR is small, focused, and independently reviewable.
They merge bottom-up: PR 1 lands first, then PR 2, then PR 3. (More on the retargeting magic in a minute.)
This isn’t about creating more work. It’s about making the unit of review smaller and keeping yourself unblocked.
What it isn’t: a rule that says “one commit per PR”. It isn’t about turning your git history into art. It’s a practical workflow for shipping complex features without sitting on your hands.
Why Long-Lived Feature Branches Are the Real Problem
The traditional PR workflow looks like this:
Branch off
mainBuild the whole feature
Open one big PR
Wait
Address feedback (which has now diverged from what you remember)
Merge
The problems compound.
Big PRs get shallow reviews. Nobody reviews 1,200 lines of code carefully. They scan for obvious bugs, eyeball the logic, approve. The subtle stuff slips through.
You’re blocked while you wait. If the next piece depends on this one, you’re stuck. Or you do what I used to do — branch off the unreviewed PR with no plan for handling the rebases later.
Feedback loops are long. A comment lands on day 2. You’ve moved on mentally. Now you reload the context on the code you wrote 48 hours ago.
Merge conflicts compound. The longer a branch lives, the more it diverges from main. Every merge becomes a negotiation.
The idea is simple:
A feature isn’t the right unit of review. A focused change is.
A Concrete Example
You’re building user authentication: a new users table, a REST endpoint, and a login form.
Traditional approach:
One branch:
feature/authOne PR: ~1,200 lines, three subsystems, no clear entry point for a reviewer
Stacked approach (same total work, split into reviewable pieces):
auth/01-users-table→ basemain(~200 lines, schema + migration)auth/02-auth-endpoint→ baseauth/01-users-table(~400 lines,POST /login)auth/03-login-form→ baseauth/02-auth-endpoint(~600 lines, React form + hook)
You open all three at once. Reviewers can start on PR 1 immediately. While they review, you keep working. When PR 1 gets feedback, you fix it on auth/01-users-table and rebase the rest of the stack on top.
The key shift: you don’t wait. You keep building on top of pending work, and reviews come in asynchronously.
Cascading Rebases: The Hard Part
Most posts about stacked PRs hand-wave this part. It’s actually where most of the pain lives.
This is what happens during review, before anything merges.
PR 1 gets feedback. You change history on auth/01-users-table — say, by amending the commit or rebasing, and every PR above it now points at a stale parent. You need to rebase the rest of the stack on top of the updated PR 1, in order.
If “rebase” is fuzzy: it means “take my commits, lift them off their old base, and stick them on top of a new one”. The commits get rewritten: same diff, new parent, new hash.
Manually, that’s:
# Update PR 1 (amend keeps the PR to one clean commit)
git checkout auth/01-users-table
# ...address feedback...
git commit --amend
git push --force-with-lease
# Rebase PR 2 onto the updated PR 1
git checkout auth/02-auth-endpoint
git rebase auth/01-users-table
git push --force-with-lease
# Rebase PR 3 onto the updated PR 2
git checkout auth/03-login-form
git rebase auth/02-auth-endpoint
git push --force-with-leaseThree things to remember:
--force-with-lease, not--force. It refuses to overwrite remote changes you haven’t seen, saving you the day a teammate pushes a commit to your branch.Conflicts compound. If your edit to PR 1 collides with PR 2, you’ll resolve it during the PR 2 rebase. If it also collides with PR 3, you’ll resolve it again during PR 3.
If you see weird duplicate commits or unexpected conflicts, plain
git rebasegot confused about where PR 2 starts. Drop to the explicit form:git rebase --onto auth/01-users-table @{u} auth/02-auth-endpoint— replays only the commits that are on the remote PR 2 branch but not on the new PR 1.
Once you've manually cascaded a few times, the value clicks.
There are many tools out there that automate this part. Pick whichever tool matches your team's appetite for new tooling.
When to Use Stacked PRs (and When Not To)
Use them when:
A feature naturally layers: schema → API → UI
Any one part is large enough to deserve a focused review on its own
You want to keep moving while reviews come in
Your team has slow review cycles
Skip them when:
The change is genuinely atomic (a one-line fix, a config update)
The layers are so interdependent that splitting them adds noise instead of clarity
The team has never done this and isn’t ready for the tooling overhead
The rule I use:
If a PR has more than ~300 lines of substantive logic, I ask whether it can be split.
Not every PR needs to be, but the question is worth asking every time.
Practical Tips for Getting Started
1. Start with the mental model, not the tooling. Do one stack by hand using the commands above. Feel the workflow. Understand why the tooling exists before installing it.
2. Name your branches clearly. Use a prefix pattern: auth/01-users-table, auth/02-api, auth/03-ui. The chain becomes visible without looking at the PR graph.
3. Write context-rich PR descriptions. Each PR should explain what it does in isolation, and where it sits in the stack. “This PR adds the schema. PR 2 adds the API. PR 3 adds the UI.” Reviewers should never have to reverse-engineer the intent.
4. Keep each PR independently runnable where you can. If PR 2 can be tested without PR 1 being merged — great. If not, say so in the description. The more each layer stands alone, the easier the reviews get.
5. Batch your feedback fixes. If PRs 1 and 2 both get comments, fix both before rebasing the stack. One cascading rebase beats two.
6. Use --force-with-lease, never --force. This is the one-line rule that saves you from clobbering a teammate’s commit.
The Culture Shift That Actually Matters
Tooling is secondary. The real shift is in how the team thinks about review.
Big PRs are a symptom of a team that has implicitly agreed that waiting is normal.
The author doesn’t want to open multiple PRs (more overhead). The reviewer doesn’t want to navigate a stack (more context).
Everyone tolerates the friction because it’s symmetric: everybody suffers the same way.
Stacked PRs break that. You do a bit more thinking upfront: splitting the work into layers. The reviewer gets a much easier job. Reviews come back faster, feedback is sharper, and merging hurts less.
Once you’ve shipped this way for a few weeks, the old workflow feels like trying to write a function with no return statement: technically possible, but you’re fighting the tool the whole way.
📌 TL;DR
Stacked PRs break a feature into a chain of small, focused PRs, each one’s base branch is the PR below it
They keep you unblocked: you keep building while reviews come in asynchronously
The hard part is cascading rebases when an earlier layer changes; tooling solves this
The workflow needs a small culture shift: many small PRs instead of a few big ones
Do one stack manually before touching tooling, that’s how you learn what the tool is buying you
Thanks for reading, and stay awesome! 🙏
Follow me on LinkedIn | Twitter(X) | Threads
Thank you for supporting this newsletter.
Consider sharing this post with your friends and get rewards.
You are the best! 🙏







I think it worthwhile to talk more about how the split should happen, especially for beginners.