Small vs Big #
There’s a very common assumption in engineering teams: the more code finished in one PR, the more productive the developer. The logic feels reasonable — more work completed, less coordination overhead. But this assumption is fundamentally wrong, and its impact is real: ineffective reviews, bugs slipping through, slowdown bottlenecks, and frustration accumulating on both sides — author and reviewer alike.
PR size isn’t a matter of personal preference or work style. It’s a decision with direct impact on review quality, delivery speed, rollback ease, and team collaboration health. This article covers why small PRs are almost always better, when big PRs are truly unavoidable, and concrete strategies for breaking large work into effectively reviewable PRs.
What Actually Happens When a Reviewer Reads a Large PR #
Before discussing strategies, it’s worth understanding what happens on the reviewer’s side when facing a large PR. This isn’t about laziness or unprofessionalism — it’s about how human cognition works.
When reading a 50-line PR, reviewers can build a complete mental model of what changed, why, and what the impact is. They can detect wrong assumptions, missed edge cases, or approaches inconsistent with existing system patterns.
When reading an 800-line PR across 15 files, something different happens:
Cognitive effects on reviewers as PR size grows:
PR 50 lines → Working memory sufficient → complete mental model
Reviewer finds: logic bugs, edge cases, design issues
PR 200 lines → Working memory starts to limit → partial mental model
Reviewer finds: obvious bugs, local issues
Starts missing: architecture consistency, inter-component interactions
PR 500 lines → Working memory overwhelmed → fragmented review
Reviewer finds: only very obvious issues
Misses: subtle bugs, implicit assumptions, indirect impacts
PR 1000+ lines → Fatigue → approval without full understanding
"LGTM" because understanding it all is impossible
The PR enters main with an almost meaningless review
This isn’t a reviewer weakness — it’s the unavoidable limit of human cognitive capacity. Large PRs structurally prevent meaningful review.
PR Size Impact on the Entire Process #
PR size doesn’t just affect review quality. It affects almost every aspect of the development cycle.
flowchart LR
A[PR size] --> B[Review Time]
A --> C[Review Quality]
A --> D[Merge Risk]
A --> E[Rollback Ease]
A --> F[Merge Conflicts]
B --> |large PR = longer| G[Slow Delivery]
C --> |large PR = weaker| H[Bugs Reach Production]
D --> |large PR = higher risk| I[Production Incidents]
E --> |large PR = harder| J[Slow Recovery]
F --> |large PR = more| K[High Rework]Review Time #
Large PRs take longer to review — and that time doesn’t grow linearly. Above 400 lines of changes, review effectiveness drops drastically while required time keeps growing.
PR size vs review time and quality:
< 100 lines → Review done in 15-30 minutes. High-quality comments.
100-300 lines → Review done in 30-60 minutes. Comments still focused.
300-500 lines → Review takes 1-2 hours. Energy runs out midway.
500-800 lines → Review often postponed ("later when I have more time").
> 800 lines → Review ends in LGTM without real reading,
or the PR sits idle for days because nobody wants to start.
Feedback Loop #
Small PRs shorten the feedback loop — the time between “code finished writing” and “code merged to main”. Large PRs lengthen it, and every passing day makes the context staler.
gantt
title Feedback Loop Comparison: Small PR vs Big PR
dateFormat HH:mm
axisFormat %H:%M
section Small PR
Write code :a1, 00:00, 2h
Review :a2, after a1, 1h
Revisions :a3, after a2, 30m
section Big PR
Write code :b1, 00:00, 8h
Waiting for reviewer :b2, after b1, 4h
Review session 1 :b3, after b2, 2h
Large revisions :b4, after b3, 3h
Review session 2 :b5, after b4, 2h
Minor revisions :b6, after b5, 1hMerge Conflicts #
A PR not merged for a long time keeps drifting away from main. The longer a PR stays open, the higher the chance of merge conflicts with other PRs merged first. Merge conflicts mean rework — and rework means wasted time.
Rollback #
When a change causes problems in production, fast rollback capability is the difference between a small incident and a big one. A small, focused PR is easy to revert: one PR, one purpose, one revert. A large PR mixing several changes can’t be partially reverted — all or nothing.
Defining “Small”: It’s Not About Numbers #
“Small” in the PR context isn’t an absolute threshold — not “under 300 lines” or “under 5 files”. A more useful definition: can one reviewer build a complete mental model of this PR in a single review session without losing context?
Factors making a PR feel BIGGER than its size:
→ Changes in a domain unfamiliar to the reviewer
(300 lines in an area the reviewer never touched feels like 1000 lines)
→ Many files interdependent in unclear ways
→ No description helping the reviewer build context
→ Mixed refactors and behavior changes — reviewers don't know what to prioritize
Factors making a PR feel SMALLER than its size:
→ An excellent description — reviewers have a mental model before reading the diff
→ Changes in one domain the reviewer is familiar with
→ Well-structured commits — reviewable commit by commit
→ Very local changes without side effects on other components
When Big PRs Are Truly Unavoidable #
There are situations where a big PR isn’t due to a lack of discipline, but because the change’s nature can’t be split without losing coherence.
Situations where a big PR is legitimate:
1. Framework migration or major refactor
→ Changes that depend on each other
→ Splitting could leave the codebase in an inconsistent state
2. Atomic feature additions
→ Features only meaningful when all components exist
→ Example: a new payment system involving gateway, webhook, and UI at once
3. Wide-impact database schema changes
→ Schema change + migration + updating all affected queries
4. Security patches needing changes in many places
→ Can't merge halfway because the security hole would remain
5. Generated code or automated changes
→ Tool-generated changes (protobuf, OpenAPI, etc.)
→ Large in size but doesn't need line-by-line review
Legitimate big PRs still need extra preparation: more detailed descriptions, clear review guides (which parts are critical, which are generated/boilerplate), and possibly a joint review session rather than async review. “A big PR can’t be avoided” doesn’t mean “a big PR can be opened without preparation”.
Strategies for Breaking Up Large PRs #
The biggest barrier to small PRs isn’t technical — it’s mental. “If the feature isn’t complete, how can it be merged?” That’s a valid question, and there are several concrete strategies to answer it.
Strategy 1: Separate Refactor from Feature #
This is the most fundamental separation and the most often ignored. Refactors and behavior changes are two different change types that must be reviewed differently.
flowchart TD
A[New feature needing a refactor] --> B[PR 1: Pure refactor]
B --> C{Behavior changed?}
C -- No --> D[Merge ✓]
D --> E[PR 2: New feature]
E --> F[Merge ✓]
C -- Yes --> G[Split again until pure]Example of correct separation:
Task: Add a CSV report export feature
(Requires refactoring the service layer first)
✗ Wrong way — one big PR:
PR: "Add CSV export feature"
- Refactor ReportService from class to function
- Add exportToCSV() method
- Add /reports/export endpoint
- Add tests for everything
✓ Right way — two small PRs:
PR 1: "refactor(report): convert ReportService to functional style"
- Pure refactor, no behavior change
- Existing tests still pass — this guarantees the refactor is safe
- Reviewers can focus: "is this refactor correct without changing behavior?"
PR 2: "feat(report): add CSV export endpoint"
- Adds the method and endpoint on top of a clean foundation
- Reviewers can focus: "is the implementation correct?"
Strategy 2: Feature Flags for Incremental Merging #
Feature flags allow code to merge into main before the feature is ready to activate for users. This is one of the most powerful techniques for avoiding long-lived branches and large PRs.
Development flow with feature flags:
Week 1 — PR 1: Infrastructure
├── Add new database schema (migration)
├── Add repository layer
└── Feature flag: ENABLE_NEW_PAYMENT = false (not active yet)
→ Merge to main, no user impact
Week 2 — PR 2: Core Logic
├── Implement the payment service
├── Unit tests for the service
└── Still behind the feature flag
→ Merge to main, no user impact
Week 3 — PR 3: API Layer
├── Add the new endpoint
├── Integration tests
└── Still behind the feature flag
→ Merge to main, QA tests on staging with the flag active
Week 4 — PR 4: Activation
├── Enable the feature flag for 10% of users (canary release)
└── Monitor metrics
→ Gradual rollout, rollback is just turning off the flag
The result:
→ No PR larger than 300-400 lines
→ Each PR reviewable with focus
→ The main branch is always deployable
→ Rollback is as easy as turning off a feature flag
Strategy 3: Stacked PRs #
A stacked PR is a technique where one PR depends on a previous PR — PR 2 branches from PR 1, not from main. This enables linear development on features with sequential dependencies.
gitGraph
commit id: "main"
branch pr-1-schema
checkout pr-1-schema
commit id: "add users schema"
commit id: "add migration"
checkout main
merge pr-1-schema id: "Merge PR 1"
branch pr-2-service
checkout pr-2-service
commit id: "add UserService"
commit id: "add unit tests"
checkout main
merge pr-2-service id: "Merge PR 2"
branch pr-3-api
checkout pr-3-api
commit id: "add /users endpoint"
commit id: "add integration tests"
checkout main
merge pr-3-api id: "Merge PR 3"How stacked PRs work:
PR 1: "feat(auth): add users and sessions schema"
→ Branches from main
→ Contains: migration, schema definitions only
PR 2: "feat(auth): implement UserService"
→ Branches from PR 1's branch (before PR 1 merges)
→ Reviewers can review PR 2 in parallel with PR 1
→ After PR 1 merges, rebase PR 2 onto main
PR 3: "feat(auth): add user registration endpoint"
→ Branches from PR 2's branch
→ Reviewers only see PR 3's diff (not the accumulation of PR 1 + PR 2)
Important notes:
→ If PR 1 needs major changes, PR 2 and PR 3 need rebasing
→ Tools like ghstack (GitHub) or Graphite make stacked PR management easier
→ Best for teams already familiar with rebase workflows
Strategy 4: Spike Branches for Exploration #
When work scope isn’t clear yet, creating a spike branch for exploration before committing to the actual PR is a better choice than directly opening a large PR that turns out to need total rework.
Spike → Draft PR → review-ready PR:
Spike branch:
→ Free exploration, no need for clean code
→ No review expectations
→ Goal: understand the problem and find the best approach
Draft PR:
→ Once the approach is found, create a PR with a description
→ Open as a Draft to get direction feedback before full implementation
→ "Is this approach right before I continue?"
Review-ready PR:
→ Clean implementation based on the spike results and Draft PR feedback
→ Already split into small PRs because the scope is now understood
Signs a PR Needs Splitting #
Signals that a PR needs splitting:
✗ Diff over 400 lines and still growing
✗ The PR title can't be summarized in one specific sentence
✗ The PR touches more than 2-3 different domains
✗ Refactors and feature changes are mixed
✗ A special meeting is needed to explain the PR to reviewers
✗ Reviewers ask for clarification about context that should be in the description
✗ The PR has been open more than 3 days without review progress
✗ A reviewer comments: "this is too big to review right now"
Questions that help decide:
→ Can this part merge on its own without waiting for other parts?
→ Does the reviewer need to understand the entire PR to review one part?
→ If this PR were reverted, would all changes have to be reverted too?
Complete Comparison: Small vs Big PR #
| Aspect | Small PR | Big PR |
|---|---|---|
| Review time | 15-60 minutes | 1-4+ hours (or postponed) |
| Review quality | High, focused | Declines with size |
| Feedback loop | Fast (hours/days) | Slow (days/weeks) |
| Merge conflicts | Rare | Frequent |
| Rollback | Easy and precise | Hard, all or nothing |
| Blast radius if buggy | Small, localized | Large, spreading |
| Historical documentation | Clear per change | Mixed context |
| Reviewer motivation | High | Low (tired before starting) |
| Deployment risk | Low | High |
Anti-Patterns to Avoid #
// ✗ Anti-pattern 1: "Might as well"
Adding unrelated changes to an in-flight PR
because "I'm already in this file anyway"
→ Every addition expands scope and lengthens review
// ✓ Create a separate PR, even for small unrelated changes
// ✗ Anti-pattern 2: Long-lived branches
Branches living for weeks without merging because "not finished yet"
→ The longer a branch lives, the bigger its PR, the more merge conflicts
// ✓ Use feature flags or stacked PRs for incremental merges to main
// ✗ Anti-pattern 3: Mixing refactors and behavior changes
Reviewers can't separate which changes "shouldn't change behavior"
from which "actually change behavior"
→ Reviews become far harder and more error-prone
// ✓ PR 1 for the refactor (existing tests must still pass)
PR 2 for the behavior change or new feature
// ✗ Anti-pattern 4: "PR only when fully complete"
Waiting until a feature is 100% done before opening a PR
→ The result is a monster PR with thousands of unreviewable lines
// ✓ PRs are collaboration tools, not just merge tools
Open a Draft PR earlier to get direction feedback faster
// ✗ Anti-pattern 5: Approving big PRs out of exhaustion
A reviewer approves a 1000-line PR not because they reviewed it well,
but because they can't bear to read it anymore
→ The most dangerous false sense of security
// ✓ If a PR is too big to review well, say so
Ask the author to split it rather than approving unread
PR Size Decision Checklist #
BEFORE OPENING A PR:
□ Can this PR be summarized in one specific sentence?
□ Are refactors and behavior changes mixed? → split them
□ Does the PR touch more than 2-3 different domains? → consider splitting
□ Is there a part that could merge on its own before others finish?
□ If > 400 lines: has splitting been considered?
WHEN A PR IS ALREADY TOO BIG AND UNAVOIDABLE:
□ Identify the most independent parts to split off first
□ Separate pure refactors into their own PR
□ Consider feature flags for incremental merging
□ Consider stacked PRs if there are sequential dependencies
□ Add a review guide in the description: what to read first,
what's generated/boilerplate that can be skimmed
AS A REVIEWER RECEIVING A BIG PR:
□ Don't approve just from reading fatigue — this is false security
□ Tell the author: "this PR is too big to review effectively"
□ Help the author figure out how to split it if they don't know how
□ If a big PR is legitimate, mark which parts need extra attention
vs which can be skimmed (generated code, boilerplate)
Summary #
- PR size directly affects review quality — the larger the PR, the more reviewers’ ability to build a complete mental model declines. This is a human cognitive limit, not a reviewer weakness.
- Small PRs aren’t slower — quite the opposite — small PRs merged quickly deliver faster than big PRs idling for days waiting for review.
- “Small” is defined by reviewability, not absolute numbers — the question isn’t “is it under 300 lines?”, but “can a reviewer build a complete mental model in one session?”
- Separate refactors from behavior changes — this is the most fundamental split. Refactors and features are two different review types that can’t be done together effectively.
- Feature flags enable merging before features finish — code can merge to main without being activated for users, eliminating the need for long-lived branches that end in big PRs.
- Stacked PRs for sequential changes — interdependent PRs can be created in a chain, enabling parallel review and incremental merging.
- Long-lived branches are the root problem — branches living for weeks produce monster PRs with merge conflicts and stale context.
- Legitimate big PRs still need extra preparation — more detailed descriptions, clear review guides, and possibly synchronous review sessions.
- Don’t approve big PRs out of fatigue — this is the most dangerous false sense of security. Better to ask the author to split it than approve without truly reading.