We've all faced this scenario: we're building a complex feature that requires database schema changes, backend API updates, frontend modifications, and comprehensive tests. We could wait until everything is done and submit a massive pull request with 50+ files changed, or we could try to break it down. But how do we split it up when each piece depends on the previous one?
This is where stacked pull requests come in. They let us break large, interdependent changes into a series of smaller, reviewable pull requests that build on each other. Instead of confronting our reviewers with a wall of changes, we give them a logical progression they can review and approve incrementally.
The challenge is that most developers don't realize Git and GitHub already support this workflow—they just need to understand how to structure their branches and manage the dependencies. Once we grasp the mechanics, stacked PRs transform how we ship complex features, making reviews faster and code quality higher.
Why Traditional Approaches Fall Short
When we're working on a substantial feature, we essentially have three options, and each has significant drawbacks. We can create one massive pull request that touches dozens of files across multiple layers of our application. This makes review extremely difficult because reviewers have to hold the entire change in their head simultaneously. They often miss important details because they're overwhelmed by the sheer volume of changes.
Alternatively, we can try to split the work into completely independent pull requests, but this often means our feature is incomplete or non-functional until all the pieces merge. We might merge database migrations that aren't being used yet, or API endpoints that no client calls. This creates a messy timeline where our main branch contains partial implementations that make the project's state unclear.
The third option is waiting to submit our pull request until everything is perfect and complete. This delays feedback until we've invested significant time in an approach that might need changes. By the time reviewers identify issues with our design decisions, we've already built substantial functionality on top of those decisions. The rework becomes expensive and frustrating.
Stacked pull requests offer a different path. We break our work into logical pieces that have clear dependencies, and we structure our branches so each piece builds on the previous one. Reviewers can understand and approve each step independently, while we can continue making progress on subsequent steps without waiting for each review to complete.
Understanding the Stack Structure
A stack of pull requests is essentially a series of branches where each branch is based on the previous one instead of all being based on the main branch. If we're building that complex feature we mentioned, our stack might look like this: a database schema branch based on main, an API implementation branch based on the schema branch, a frontend integration branch based on the API branch, and a testing branch based on the frontend branch.
Each pull request targets the branch it's based on rather than targeting main directly. The schema PR targets main, the API PR targets the schema branch, the frontend PR targets the API branch, and so on. This creates a dependency chain that mirrors the logical dependencies in our code.
When the first PR merges to main, we update the target of the second PR to point to main instead of the now-merged first branch. This cascading merge pattern continues up the stack until all pieces are integrated. The key insight is that we're using branch targeting to manage our dependencies rather than waiting for sequential reviews.
This structure gives us several advantages. Reviewers can focus on one coherent change at a time. Each PR has a clear scope and purpose. We can continue working on later pieces in the stack while earlier pieces are in review. If we need to make changes based on review feedback, those changes are isolated to a single PR rather than requiring updates across a massive changeset.
Setting Up Your First Stack
Creating a stack of pull requests starts with planning how we'll break down our work. We need to identify the natural boundaries where one piece of functionality is complete enough to review independently, even if subsequent pieces depend on it. For our database-to-frontend feature example, the migration that creates new tables is a natural boundary. The API endpoints that use those tables are another boundary. The UI components that call those endpoints form yet another boundary.
Once we've identified our logical breaks, we create our branches. We start from
main and create our first branch for the database schema changes. We'll call it
feature/user-preferences-schema. We implement the schema changes, write the
migration, and create tests that verify the migration works correctly. This
becomes our first PR, and it targets main.
Before we create the second branch, we make sure our first branch is in a
committable state. Then we create our second branch based on the first:
git checkout -b feature/user-preferences-api from the
feature/user-preferences-schema branch. Now we implement our API endpoints,
knowing we can use the database schema we defined in the first branch. This
second PR will target feature/user-preferences-schema rather than main.
We continue this pattern for each piece of functionality. The third branch,
perhaps feature/user-preferences-ui, is created from the API branch and
implements the frontend. The fourth branch adds comprehensive integration tests.
Each branch builds on the work from the previous branch, and each PR targets its
parent branch.
When we create the pull requests, we write clear descriptions that explain not just what each PR does, but where it fits in the overall stack. We might include a section in each PR description that shows the complete stack structure, so reviewers understand they're looking at one piece of a larger change.
Managing the Stack as Reviews Progress
The real complexity of stacked PRs emerges when we need to make changes based on review feedback. If a reviewer suggests changes to our database schema—the first PR in our stack—those changes need to flow through all the subsequent branches that depend on that schema.
When we make changes to the first branch, we need to rebase the second branch
onto the updated first branch. We checkout our API branch and run
git rebase feature/user-preferences-schema. Git replays our API changes on top
of the updated schema changes. If there are conflicts, we resolve them. Then we
force-push the API branch to update its pull request.
This rebase operation needs to cascade through the entire stack. After rebasing the API branch, we need to rebase the UI branch onto the updated API branch, and then rebase the testing branch onto the updated UI branch. Each rebase might introduce conflicts that need resolution. This is where stacked PRs can become tedious if we're not careful about keeping our branches focused and our commits clean.
One strategy that makes this easier is keeping each PR in the stack as small as possible. Smaller changes mean fewer conflicts when rebasing. Another helpful practice is resolving feedback on earlier PRs before creating later ones. If we can get the schema PR close to approval before we create the API PR, we reduce the likelihood of needing to propagate changes through multiple branches.
As PRs in our stack get approved and merged, we need to update the targets of the remaining PRs. When our schema PR merges to main, we need to change the base branch of our API PR from the schema branch to main. GitHub makes this straightforward—we can edit the pull request and change the base branch. After changing the base, we usually need to rebase our API branch onto the updated main branch to ensure it has the latest changes.
Git Commands for Stack Management
The fundamental Git operations for managing stacked PRs revolve around creating well-structured branches and rebasing them as the stack evolves. When we're creating our initial stack, we're using straightforward branch creation, but we need to be disciplined about where each branch originates.
Creating the first branch looks like any other feature branch:
git checkout main, git pull origin main to ensure we're up to date, and
git checkout -b feature/schema-changes. We make our changes, commit them, push
the branch, and create a PR targeting main.
For the second branch, we checkout from the first branch instead of from main:
git checkout feature/schema-changes and then
git checkout -b feature/api-layer. Now our API branch contains all the commits
from the schema branch plus any new commits we add for the API work. When we
create the PR for this branch, we set the base to feature/schema-changes
instead of main.
The complexity comes when we need to update branches after changes. If we push
updates to our schema branch after review feedback, we need to get those changes
into our API branch. We use git checkout feature/api-layer followed by
git rebase feature/schema-changes. This replays our API commits on top of the
updated schema commits.
After rebasing, our branch history has changed, so we need to force-push:
git push --force-with-lease origin feature/api-layer. The --force-with-lease
option is safer than a regular force push because it fails if someone else has
pushed to the branch since our last fetch. This protects us from accidentally
overwriting a teammate's changes.
When the first PR merges, we need to rebase our second branch onto main instead
of the now-deleted feature branch. We checkout our API branch, fetch the latest
from origin to get the merged changes, and run git rebase origin/main. This
rebases our API changes directly onto main, removing the intermediate schema
commits since they're already in main.
For more complex scenarios where we have multiple commits in a branch that need
reorganization, we can use interactive rebase: git rebase -i origin/main. This
opens an editor where we can reorder commits, squash related changes together,
or edit commit messages to better reflect the logical structure of our changes.
Tools That Simplify Stack Management
While we can manage stacked PRs with raw Git commands, several tools have emerged to make the workflow less tedious. These tools automate the repetitive parts of stack management—creating branches, updating base targets, rebasing through the stack—while still using Git under the hood.
Graphite is one of the more polished solutions for
stacked PRs. It introduces commands like gt create to create a new branch in
your stack and gt submit to create or update pull requests for all branches
in your stack. When you make changes to a lower branch in the stack, Graphite
can automatically rebase all dependent branches with a single command. It also
provides a visual representation of your stack, making it easier to understand
the dependency structure.
The tool maintains metadata about your stack structure, so it knows which
branches depend on which. When you need to rebase the entire stack after changes
to a base branch, you can run gt restack instead of manually rebasing each
branch. Graphite also handles updating PR base branches when lower PRs merge,
removing much of the manual GitHub interface work.
GitHub's gh CLI tool, while not specifically designed for stacked PRs, provides
scripting capabilities that can simplify stack management. We can write shell
scripts that use gh pr create and gh pr edit to automate the creation and
updating of PRs in our stack. The gh CLI's ability to interact with PRs via
command line means we can automate the base branch updates that would otherwise
require clicking through the GitHub interface.
git-branchstack is a lighter-weight option that focuses specifically on the rebasing workflow. It helps you track which branches depend on each other and provides commands to rebase the entire stack at once. It doesn't integrate with GitHub's PR interface like Graphite does, but it solves the painful problem of cascading rebases through multiple branches.
For teams using Meta's infrastructure or who want to adopt their approach, Sapling provides a complete rethinking of the Git workflow with native support for stacked changes. While it's a more substantial investment than adding a tool on top of Git, it demonstrates how powerful the stacked workflow can be when it's built into the version control system itself.
When Stacked PRs Make Sense
Stacked pull requests aren't the right choice for every situation. They add complexity to our workflow, and that complexity only pays off when we're dealing with changes that have genuine dependencies and would benefit from incremental review.
The clearest use case is large features that have natural layers or phases. When we're building a feature that requires database changes, backend logic, API endpoints, frontend components, and tests, stacking lets us get each layer reviewed as we complete it. Reviewers can verify our database design before we build the API on top of it, which means we catch design issues early rather than after we've implemented the entire stack.
Stacked PRs also shine when we're doing significant refactoring work that needs to happen in stages. We might want to extract common functionality into a shared utility, update all the call sites to use the new utility, and then optimize the utility implementation. Each step depends on the previous one, but each is also independently reviewable. Reviewers can verify we extracted the logic correctly in the first PR, check that we updated all call sites in the second PR, and evaluate our optimization approach in the third PR.
Technical migrations often benefit from stacking. If we're migrating from one library to another, we might start by adding the new library alongside the old one, then progressively update modules to use the new library, and finally remove the old library. Each PR represents a step in the migration that's independently reviewable and mergeable, reducing the risk of a big-bang migration that breaks everything at once.
The key question to ask is whether the intermediate states are valuable for review purposes. If breaking our work into pieces creates artificial boundaries that don't help reviewers understand the changes, we're probably forcing the stacked approach where it doesn't fit. But when each PR in our stack represents a logical unit of work that reviewers can evaluate independently, stacking makes the review process significantly better.
When to Avoid Stacking
Stacked PRs add coordination overhead, and sometimes that overhead isn't worth it. For smaller features that might touch multiple parts of the codebase but don't have clear dependency layers, a single well-structured PR is often clearer. If we find ourselves creating PRs where each one only makes sense in the context of the others, we're probably over-engineering our review process.
Teams that are new to Git or uncomfortable with rebasing might struggle with stacked PRs. The workflow requires confidence with Git operations like rebasing, force-pushing, and resolving conflicts. If our team isn't comfortable with these operations, the stacked approach can create confusion and frustration. It's worth building Git proficiency with simpler workflows before introducing the additional complexity of stacks.
When we're working on truly experimental code where the design might change significantly based on feedback, stacking can backfire. We might create a stack of PRs only to realize after review of the first one that our entire approach needs to change. We've now added the complexity of managing a stack without getting the benefit of incremental approval. For exploratory work, a draft PR or design document might be better starting points.
Time pressure sometimes argues against stacking. Creating, managing, and reviewing a stack of PRs takes more time than reviewing one PR. If we need to ship something urgently and our changes are relatively straightforward, the overhead of stacking might not be worth it. We need to balance the benefits of better review against the reality of our deadlines.
Projects with very rapid merge cadences might find stacking creates more conflicts than it solves. If our main branch changes significantly every few hours, keeping our stack rebased and up-to-date becomes a constant battle. In high-velocity environments, it might be better to work in small, independent PRs that merge quickly rather than building dependencies between PRs.
Best Practices for Stacked Success
Clear communication becomes essential when working with stacked PRs. Each PR in our stack should have a description that explains not just what the PR does, but where it fits in the larger feature. We might include a section that lists all the PRs in the stack with links, so reviewers understand they're looking at step three of five rather than a standalone change.
Commit hygiene matters more with stacked PRs than with regular workflows. Each commit in our stack should represent a logical unit of work, because these commits will be rebased multiple times as we update our stack. Messy commits with debugging code, temporary changes, or unclear purposes make rebasing harder and increase the likelihood of conflicts. Using interactive rebase to clean up our commits before pushing makes our stack more maintainable.
We should create our stack incrementally rather than all at once. Instead of creating all five branches and PRs upfront, we create the first PR, get it close to approval, and then create the second PR. This reduces the amount of rebase cascading we need to do when early PRs need changes. It also gives us flexibility to adjust our approach based on review feedback before we've invested time in the entire stack.
Keeping PRs in our stack small and focused makes the entire workflow more manageable. Each PR should ideally change one logical thing. If we find ourselves describing a PR with multiple "and" clauses—"this updates the API and adds the UI and includes some tests"—we probably need to split it into multiple PRs. Smaller PRs are easier to review, faster to rebase, and less likely to have conflicts.
We need to be responsive to review feedback on earlier PRs in our stack. If a reviewer suggests changes to PR one, addressing those changes quickly prevents us from building more work on top of a foundation that's going to change. It's tempting to keep working on PR four while PR one is in review, but that creates more rebase work if PR one needs significant changes.
Handling Conflicts Gracefully
Conflicts are inevitable when working with stacked PRs, especially when we're making changes based on review feedback. The key to managing conflicts is understanding where they come from and having a methodical approach to resolving them.
When we rebase a branch that has conflicts, Git pauses the rebase and asks us to
resolve the conflicts in each commit. We need to edit the conflicting files,
understand what each side of the conflict represents, and decide how to
integrate the changes. After resolving conflicts in a commit, we run git add
on the resolved files and git rebase --continue to proceed to the next commit.
Sometimes conflicts happen because we've made the same logical change in multiple places in our stack. If we added a new function parameter in our API branch and then used that parameter in our UI branch, but review feedback asked us to rename the parameter, we'll have conflicts when we rebase the UI branch. Understanding the logical connection between the changes helps us resolve the conflict correctly.
We can reduce conflicts by being strategic about how we structure our changes. If we know certain files change frequently in our codebase, we try to make our changes to those files in the earliest possible PR in our stack. This minimizes the number of times those changes get rebased. We also try to avoid making unnecessary formatting changes or refactoring that isn't directly related to our feature, because these changes increase the likelihood of conflicts.
When conflicts become overwhelming, it's worth considering whether our stack structure needs adjustment. If we're constantly resolving the same conflicts across multiple branches, maybe those changes should be in a single PR. Or maybe we need to reorder our stack so that related changes are grouped together rather than spread across multiple layers.
Reviewing Stacked PRs
When we're on the reviewing side of stacked PRs, we need to adjust our approach slightly compared to reviewing standalone changes. Each PR in a stack should be reviewed as a complete unit that makes sense on its own, but we also need to keep the broader context in mind.
We should start by reviewing the first PR in the stack before looking at later ones. This mirrors the dependency structure and helps us understand the foundation before we evaluate what's built on top of it. If we spot issues in the first PR, those issues might propagate through the entire stack, so catching them early saves everyone time.
Each PR should include enough context that we can review it independently. The author should explain how this PR fits into the larger feature, what assumptions it makes based on previous PRs in the stack, and what later PRs will build on this foundation. If we find ourselves confused about why certain changes exist, it might indicate the PR description needs more context.
We should pay attention to the commit structure within each PR. Well-structured commits make it easier to understand the progression of changes and to pinpoint exactly where something might be problematic. If commits are messy or unclear, we can ask the author to clean them up with interactive rebase before we complete our review.
When suggesting changes to PRs early in a stack, we should consider the impact on later PRs. If we're asking for substantial restructuring of the first PR, we should acknowledge that this will require updates to the dependent PRs. Sometimes it's worth having a quick synchronous discussion about major changes rather than going through multiple rounds of async review.
The Cultural Shift Required
Adopting stacked PRs as a team requires more than just learning Git commands. It represents a shift in how we think about breaking down work and coordinating on complex changes. Teams that successfully adopt stacked PRs typically have strong Git fundamentals and a culture of thoughtful code review.
We need to establish team norms around when stacking makes sense and how we'll structure our stacks. Should every large feature use stacked PRs, or is it optional based on developer preference? How do we communicate about stacks to ensure reviewers understand the context? Who reviews different PRs in the stack—should the same reviewer handle the entire stack for consistency, or should we distribute review work?
Documentation becomes more important when using stacked PRs. We should document our team's approach to stacking: how we create stacks, how we handle rebasing, what tools we use, and what we expect in PR descriptions for stacked changes. New team members need guidance to understand this workflow, especially if they're not familiar with stacking from previous teams.
We might need to adjust our CI/CD pipeline to handle stacked PRs effectively. Our automated tests should run on each PR in the stack, even though the PRs depend on each other. We need to think about how we handle merge conflicts that arise from changes to main while our stack is in review. Some teams use automation to keep their entire stack rebased on main automatically.
The investment in stacked PRs pays off when we're regularly shipping complex features that benefit from incremental review. For teams that mostly work on small, independent changes, the overhead might not be worth it. The key is honestly evaluating whether the workflow matches our actual development patterns.
Making Stacked PRs Work for You
Stacked pull requests represent an advanced Git workflow that solves real problems with reviewing complex changes. When we have a large feature with clear dependencies and would benefit from incremental feedback, stacking transforms an overwhelming review into a series of manageable steps.
The workflow requires more Git proficiency than basic feature branch development, and it requires more coordination with reviewers who need to understand the stack structure. But for teams that invest in building these skills, stacked PRs become a powerful tool for shipping complex work without sacrificing code review quality.
The key to success is starting small. We don't need to use stacked PRs for every feature—we should use them when they clearly improve the review experience. We should get comfortable with the basic mechanics of creating and rebasing a simple two-PR stack before attempting complex multi-layer stacks. We should establish clear team norms about how we'll use stacking and what we expect from PR descriptions in a stack.
We should also recognize that tooling can significantly reduce the friction of stacked PRs. Whether we use Graphite, write our own scripts with the gh CLI, or adopt another solution, having automation for the repetitive parts of stack management makes the workflow more sustainable.
Ultimately, stacked PRs are about respecting our reviewers' time and cognitive load. We're choosing to invest extra effort in structuring our changes so that reviewers can provide better feedback with less mental overhead. That investment reflects a commitment to code quality and team effectiveness that goes beyond simply getting our code merged.
