Code Review

How to Review Stacked Pull Requests in the Right Order

A stack of twelve pull requests, each one green in CI, each one dependent on the one beneath it. The reviewer opens the top of the stack first, reads a diff that references a function that does not exist yet, and spends twenty minutes reconstructing context that was sitting three branches down. This is the most common failure mode in stacked review, and it has nothing to do with the reviewer's skill. It is an ordering problem.

Stacked pull requests are a workflow where a series of small changes are built on top of each other, each branch depending on its parent, so a feature that would be one sprawling diff becomes four or six coherent ones. The review question is not whether to review them. It is which one first, and why the answer is almost always the bottom.

The bottom-up rule sounds obvious written down. In practice, teams violate it constantly, because the top of the stack is usually the most interesting part and the bottom is usually plumbing. That preference is exactly backwards for comprehension.

Why the bottom of the stack is the only place to start

A stacked PR is a sequence of definitions and uses. The bottom branch introduces something: a schema column, a helper, a new type, a route. The next branch consumes it. By the time a reviewer reaches the third PR, half its diff reads as obvious because the earlier context has already been loaded.

Read the stack top-down and the reviewer meets every use before its definition. A new parameter appears with no explanation of where it came from. A test asserts behavior that the earlier PR established. The reviewer's working memory has to hold questions open across multiple diffs, and working memory is the scarcest resource in code review.

Bottom-up review converts that open-question backlog into a settled base. Each PR is judged against a foundation the reviewer has actually read. The final PR in the stack, the one that wires everything together, becomes the easiest read of the set rather than the hardest.

Reading the stack map before reading any code

Before opening a single diff, a reviewer should spend two minutes on the stack's shape. Most tools (Graphite, GitHub's stacked PR extensions, plain branch chains) show the dependency order somewhere. The map is the syllabus.

Three things are worth extracting from that map. First, which PR is the true base: the one targeting main or the trunk branch. Second, which PRs are independent siblings versus strictly linear. Third, where the largest diff sits. A stack of six where the fourth PR carries four hundred lines and the rest carry forty each has a different review plan than an even stack.

The map also exposes a smell. If the bottom PR is tiny and the second is enormous, the stack was probably sliced wrong, and the reviewer should say so before spending an hour on it. A healthy stack has a roughly even distribution, with the base slightly smaller than the middle.

Reviewing the base PR as if it were the only one

The base PR gets the strictest treatment. This is the branch that everything above it inherits, so a mistake here propagates upward and is cheapest to fix now. A reviewer should read the base diff with no awareness of the stack at all, as though it were a standalone change headed for main.

That framing matters. The base PR has to be correct, complete, and mergeable on its own terms. If it only makes sense because of what comes next, the slicing is wrong. A base branch that adds a database column but no code to use it should still be a defensible, standalone change: a migration that ships safely and does nothing yet.

Comments on the base should be resolved before moving up. Not just acknowledged. Resolved. A base-level concern that gets waved through as "we'll address it later in the stack" tends to become a permanent scar, because later PRs build on the assumption that the base is settled.

The dependent PRs: read the delta, not the accumulated diff

Here is where tooling betrays most reviewers. A pull request against its parent branch shows only the delta. A pull request against main shows every change from the base upward, and that accumulated view is a trap. It re-shows work already reviewed, it inflates the diff, and it encourages skipping.

Review each dependent PR against its immediate parent. The delta is the unit of review. If the tool defaults to comparing against main, change the base. GitHub lets a PR target any branch, and the reviewer should insist that the target is the parent, not the trunk.

Within the delta, the question shifts. The base PR was judged for correctness and standalone sense. A dependent PR is judged for how it uses what the base established. Does it consume the new helper correctly? Does it handle the new column's null case? Does it respect the contract the base branch implied?

The delta read is also the only honest way to catch scope creep. A dependent PR that adds something the stack map did not promise is a slicing problem surfacing late. Naming it at this level, before the next PR builds on it, is far cheaper than catching it at the top.

When a parent changes after review has begun

Stacks are alive. A base PR gets a review comment, the author force-pushes a fix, and every child branch now sits on a different foundation than the one the reviewer approved. This is the most disruptive event in stacked review, and it has a specific recovery pattern.

Rebases are expected in stacked workflows. What matters is whether the rebase altered the semantic content of the base. A rebase that only reorders commits or resolves a trivial conflict leaves the children valid. A rebase that changes behavior invalidates every review above it, because each child was judged against a contract that no longer holds.

The reviewer's move is to ask for a one-line summary of what changed at the base, not a fresh read of the whole stack. If the summary is "conflict resolution only," the upper reviews stand. If the summary is "the helper now returns a tuple instead of a single value," the upper reviews need at least a targeted re-read of every call site.

Teams that treat a base change as a full stack re-review burn out fast. Teams that never re-review after a base change ship subtle inconsistencies. The middle path is a stated diff-of-diffs: what changed at the base, and which children touch it.

Merging order is not the same as reviewing order

A reviewer can read bottom-up and still merge in whatever order the release plan requires. The two orders are independent. Review order is about comprehension; merge order is about deployment safety. Conflating them produces the worst of both: reviewers who read top-down because that is how it will ship, and merges that happen in the wrong sequence because nobody tracked the dependency.

The one overlap worth respecting: a stack should merge bottom-up by default, because each child assumes its parent is in the trunk. Merging a middle PR before its base leaves the trunk transiently broken. The safe pattern is to merge the base, wait for CI on trunk, then merge the next, and so on.

Some teams merge the whole stack atomically through a single queue. That works, but it hides which PR was the actual cause if trunk goes red. A reviewer who has read bottom-up already knows which branch to look at first when the smoke tests fail.

Splitting the review load across people

Large stacks invite parallel review, and parallel review done carelessly is worse than serial. If two reviewers take the top and bottom half of a six-PR stack simultaneously, the top-half reviewer is judging code against a base they have not read. Their comments will be confidently wrong about half the time.

The workable split is by concern, not by position. One reviewer owns correctness of the base and the data layer. Another owns the API surface across the whole stack. A third owns tests. Each of them still reads bottom-up, but along their own axis. The stack is a matrix, and reviewers walk columns, not rows.

The alternative, one reviewer reading the whole stack serially, is slow but coherent. For a stack of four or fewer, that is usually the right call. Beyond six, the concern-split pays for itself, provided every participant honors the bottom-up rule within their slice.

The comment that belongs one PR down

Halfway up a stack, a reviewer finds a flaw that actually originates in the base. The instinct is to comment where the flaw is visible, which is in the current PR. That instinct is wrong. The comment belongs on the PR that introduced the problem.

Leaving the comment at the top means the fix happens at the top, which means the base remains flawed and every future child inherits it. Moving the comment down forces the author to fix the foundation, rebase, and propagate. Painful in the moment, correct in the aggregate.

This is the single habit that separates reviewers who are comfortable in stacks from reviewers who merely tolerate them. The willingness to relitigate a lower PR after already approving it is the price of keeping a stack sound.

A short protocol for the next stack on the queue

Read the stack map first. Confirm the base targets the trunk. Read the base as a standalone change and resolve every comment on it before opening the next. Read each child against its parent, delta only, and stop reading the moment the diff drifts into scope the stack map did not promise. When a base rebases, ask for a semantic summary before deciding what to re-read. Merge bottom-up, and when the trunk goes red, look at the lowest unmerged branch first.

None of this is exotic. It is the ordinary discipline of reading definitions before uses, applied to a workflow that makes the violation easy and the correction cheap. The stack is a story told in order. Reviewers who read it in order finish faster, comment less, and catch the errors that actually matter.

Leave a Reply

Your email address will not be published. Required fields are marked *

Back to top button