Have you ever finished a feature, opened a Pull Request (PR), and then… sat there?
You know you need to build the next part of the feature, but it depends on the code you just submitted. You don’t want to dump a massive, 2,000-line PR on your senior engineer (they will inevitably hate you for it), but you also can’t afford to be blocked for two days waiting for an approval.
If this sounds familiar, you need to learn how to use Stacked PRs.
In this post, we’ll break down exactly what Stacked PRs are, how to implement them in your Git workflow, and how to avoid the common rebase traps that catch developers off guard.
What Are Stacked PRs?
Instead of cramming a massive feature into a single, unreviewable pull request, stacking is the practice of breaking your work into a sequence of smaller, dependent branches. Each branch adds a logical chunk of the feature, and each gets its own separate PR.
Because Branch B is based on Branch A (rather than main), you can keep working forward while your teammates review your code asynchronously.
Stacked Pull Request (PR) Workflow
This document provides a visual and step-by-step flow for executing Stacked PRs to keep your development unblocked.
Visual Workflow
flowchart TD
%% Base Branching
Main[fa:fa-code-branch main] -->|1. Branch off main| PR1_Branch(feature/1-db-schema)
PR1_Branch -->|2. Open PR 1| PR1_Target[PR #1 Base: main]
%% Stacked Branching 1
PR1_Branch -->|3. Branch off PR 1| PR2_Branch(feature/2-api-endpoints)
PR2_Branch -->|4. Open PR 2| PR2_Target[PR #2 Base: feature/1-db-schema]
%% Stacked Branching 2
PR2_Branch -->|5. Branch off PR 2| PR3_Branch(feature/3-frontend-ui)
PR3_Branch -->|6. Open PR 3| PR3_Target[PR #3 Base: feature/2-api-endpoints]
%% Review and Merge Flow
classDef merge fill:#2ea44f,stroke:#2ea44f,color:white;
PR1_Target -.->|7. Merged| Main_Updated1[main updated]:::merge
Main_Updated1 -.->|8. Update PR 2 Base| PR2_Target_New[PR #2 Base: main]
PR2_Target_New -.->|9. Merged| Main_Updated2[main updated]:::merge
Step-by-Step Command Flow
1. Create the Base PR (PR #1)
Branch off main for your foundational work.
git checkout main
git pull origin main
git checkout -b feature/1-db-schema
# ... write code ...
git add .
git commit -m "feat: db schema"
git push -u origin feature/1-db-schema
- GitHub/GitLab Action: Open PR #1 and set the target/base branch to
main.
2. Create the Dependent PR (PR #2)
Don’t wait for PR #1 to merge. Branch directly off feature/1-db-schema.
git checkout feature/1-db-schema
git checkout -b feature/2-api-endpoints
# ... write code ...
git add .
git commit -m "feat: api endpoints"
git push -u origin feature/2-api-endpoints
- GitHub/GitLab Action: Open PR #2 and set the target/base branch to
feature/1-db-schema.
3. Handling Code Review Updates (The Ripple Effect)
If Reviewer A requests changes on PR #1, you must update PR #1 and then cascade those changes to PR #2.
# Update PR #1
git checkout feature/1-db-schema
# ... make requested changes ...
git commit -am "fix: review comments on schema"
git push origin feature/1-db-schema
# Cascade changes to PR #2
git checkout feature/2-api-endpoints
git rebase feature/1-db-schema
git push -f origin feature/2-api-endpoints
4. Merging the Stack
When PR #1 is approved and merged into main:
- PR #1 is merged.
- Go to PR #2 in your GitHub/GitLab UI.
- Change the base branch of PR #2 from
feature/1-db-schematomain. - (Optional but recommended) Pull the latest main locally and rebase PR #2 if your team uses squash-merging.
git checkout main
git pull origin main
git rebase --onto main feature/1-db-schema feature/2-api-endpoints
git push -f origin feature/2-api-endpoints
A Real-World Use Case
Imagine you are building a new user profile dashboard. To make this work, you need to:
1. Update the database schema.
2. Build the backend API endpoints.
3. Create the frontend React components.
The Anti-Pattern (One Giant PR):
You build everything on one branch (feature/user-dashboard). When you open the PR, it has 45 changed files. Your reviewer takes one look, sighs, and puts it off until Friday.
The Stacked Approach: Instead, you create three isolated but dependent branches.
-
PR 1: Database schema changes. (Easy to review, gets merged quickly).
-
PR 2 (Depends on PR 1): Backend API.
-
PR 3 (Depends on PR 2): Frontend UI.
Your reviewers get small, easily digestible chunks of code. You get to keep writing code without waiting.
How to Stack PRs (Step-by-Step)
Here is how you actually execute a stack using standard Git commands.
Step 1: Create the Base Branch
Checkout from main and make your first set of changes (e.g., the database schema).
git checkout -b feature/1-db-schema main
# Make your commits...
git commit -m "feat: update user schema"
git push origin feature/1-db-schema
Open PR #1: Base it against main.
Step 2: Stack the Next Branch
Instead of branching from main again, branch directly off your first feature branch.
git checkout -b feature/2-api-endpoints feature/1-db-schema
# Make your commits...
git commit -m "feat: add user profile API"
git push origin feature/2-api-endpoints
Open PR #2: In GitHub/GitLab, explicitly set the base of this PR to feature/1-db-schema, not main.
Step 3: Keep Climbing
Repeat the process for your frontend branch.
git checkout -b feature/3-frontend-ui feature/2-api-endpoints
# Make your commits...
git commit -m "feat: build profile dashboard UI"
git push origin feature/3-frontend-ui
Open PR #3: Base it against feature/2-api-endpoints.
The Gotchas: Potential Pitfalls & Mistakes
Stacked PRs are basically a superpower, but they come with a specific set of Git challenges.
1. The “Cascading Rebase” Nightmare
If a reviewer requests changes on PR #1, you have to update feature/1-db-schema. But now, PR #2 and PR #3 are out of sync. If you aren’t careful, you will end up with merge conflicts across your entire stack.
The Fix: You must rebase the dependent branches onto the updated base branch.
# Update the bottom of the stack
git checkout feature/1-db-schema
# ... make requested changes and commit ...
# Rebase the next branch up
git checkout feature/2-api-endpoints
git rebase feature/1-db-schema
# Rebase the top of the stack
git checkout feature/3-frontend-ui
git rebase feature/2-api-endpoints
(Pro-tip: Use git rebase --onto for more complex branch surgery if intermediate commits get messy).
2. Merging Out of Order
If you squash and merge PR #2 before PR #1, you will break the Git history of your stack. Always merge from the bottom up. Once PR #1 is merged into main, you must re-target PR #2’s base from feature/1-db-schema to main before merging it.
3. Reviewer Confusion
If reviewers don’t realize PR #3 depends on PR #2, they might get confused looking at the diffs. Always make it explicitly clear in the PR description that this is part of a stack, and link the dependent PRs.
Summary
Stacked PRs optimize for developer momentum. By breaking monolithic feature branches into small, logically sequential pieces, you reduce cognitive load for your reviewers and eliminate your own wait times.
While it requires a firmer grasp on Git rebasing, the trade-off in velocity is well worth the learning curve. If standard Git commands feel too manual for this workflow, check out CLI tools designed specifically for this, like Graphite or Git Town, which automate the tedious rebasing steps.
Ready to try it? On your next feature, try splitting your first logical chunk (like a database migration or interface definition) into its own PR, branch off it, and keep coding. Let us know how your team reacts to the bite-sized reviews!