Small pull requests win
I've reviewed thousands of pull requests across e-commerce platforms, CRM systems, and real-time trading engines. The pattern is unmistakable: a 200-line PR gets thorough scrutiny and thoughtful feedback. A 2,000-line PR gets an approval because reviewers are exhausted. Small pull requests win because they force clarity, enable speed, and make mistakes recoverable.
The review bottleneck
Code review is where quality happens. It's also where most teams lose momentum. When a developer opens a 2,000-line pull request touching authentication, database migrations, and API endpoints simultaneously, the reviewer faces a cognitive mountain. They skim. They miss edge cases. They approve to unblock other work.
I saw this kill a ride-hailing platform's stability. A developer refactored the booking service while adding new fare logic while fixing timezone bugs—all in one PR. The reviewer, already context-switched, approved in 20 minutes. Two days later, surge pricing broke in Bangkok during peak hours because the database migration had a race condition nobody caught.
Small PRs eliminate this. A 150-line change focused on a single concern—say, extracting a timezone utility function—takes 10 minutes to review thoroughly. The reviewer can reason about the entire change. They can trace implications. They spot the off-by-one error that would have cost hours in production debugging.
Easier to review thoroughly
Thorough review requires holding context. Human working memory has limits. When you review code, you're building a mental model: What does this change do? What breaks if this assumption fails? Are there race conditions? Is error handling complete?
A small PR keeps that model manageable. At NestJS projects I've worked on, I started enforcing a soft 300-line limit per PR. Reviewers could actually reason about the entire change. They asked better questions. They caught subtle bugs. Most importantly, they didn't skip sections because the diff was too large.
Consider this example. A developer needs to add caching to a PostgreSQL query:
// Before: Direct database query
async getUser(id: string) {
return this.db.query(
'SELECT * FROM users WHERE id = $1',
[id]
);
}
// After: With Redis caching
async getUser(id: string) {
const cached = await this.cache.get(`user:${id}`);
if (cached) return cached;
const user = await this.db.query(
'SELECT * FROM users WHERE id = $1',
[id]
);
await this.cache.set(`user:${id}`, user, 3600);
return user;
}
This is a 20-line change. A reviewer can hold the entire logic in their head. They'll ask: What happens if the cache is stale? Is the TTL correct for this use case? Should we invalidate on user updates? These are the right questions, and they emerge naturally from focused review.
Now imagine this logic bundled with three other refactors in a 1,500-line PR. The reviewer never gets there. They approve the caching logic without understanding its failure modes.
Faster feedback and fewer conflicts
Large PRs take longer to merge. While one developer waits for review on their 2,000-line change, five other developers push to main. Merge conflicts multiply. Rebasing becomes a nightmare. I've seen teams spend an entire day resolving conflicts on a single PR.
Small PRs merge in hours. This keeps the main branch moving. Other developers don't hit conflicts because there's less surface area to collide with. Your deployment pipeline stays active.
On a SaaS platform I worked on, we cut average PR size from 800 lines to 250 lines. The result: merge conflicts dropped by 70%. Cycle time—from opening a PR to deploying—fell from 3 days to 8 hours. Not because we worked faster, but because small changes integrate cleanly.
There's a secondary benefit: feedback arrives faster. Small PRs spend less time in review queue. When feedback does come, it's focused and actionable. The developer can iterate quickly because the scope is bounded.
Simpler rollbacks
Production breaks. It will happen. When it does, rollbacks are your safety net. A small PR is a clean rollback: one commit, one feature, gone. You're back to the previous state in seconds.
A 2,000-line PR? That's a partial rollback nightmare. You've touched authentication, caching, database schema, and API responses. Rolling back breaks user accounts or leaves orphaned data. Your on-call engineer is now untangling dependencies at 3 AM.
I experienced this on a travel platform. A developer bundled booking validation changes, payment processing updates, and notification refactoring into one 1,800-line PR. Three hours after deploy, payment confirmation emails weren't sending. We couldn't rollback the entire PR because booking validation was working fine. We had to cherry-pick reverts, pray we didn't break something else, and manually reconcile data. It took four hours.
If that payment refactoring had been a separate, small PR, we'd have identified and rolled back in 10 minutes.
Split by refactor, feature, and cleanup
The pattern I use: one PR per concern. Refactoring, features, and cleanup are different concerns. They should live in different PRs.
Refactoring PRs
Extract a utility, rename a variable, reorganize a module—these are pure refactors. No behavior changes. Reviewers check: Does the refactor preserve logic? Are the new abstractions sound? Will future developers understand the structure?
A refactoring PR can be 300+ lines if it's thorough—extracting a complex service into utilities, for instance—because the reviewer's job is mechanical: verify equivalence. This is one of the few cases where size is forgivable.
Feature PRs
A feature PR adds or changes behavior. Keep it small. One API endpoint, one workflow, one calculation. A small feature PR forces you to design tightly. You can't bundle unrelated logic. The feature is testable, reviewable, and deployable on its own.
Example: adding a discount code API endpoint. One file, one handler, one test. It doesn't refactor the pricing engine or rewrite the cart. It does one thing.
Cleanup PRs
Remove dead code, update dependencies, fix linting issues—separate these from features and refactors. A cleanup PR is low-risk and high-confidence. Reviewers can approve quickly. There's no behavioral risk.
Common mistakes
Mistake 1: Artificial splits. Breaking a logical feature into five tiny PRs just to hit a line-count target wastes time. A coherent feature of 400 lines is better than five 80-line PRs that can't be reviewed independently. The goal is logical smallness, not arbitrary smallness.
Mistake 2: Small PR, bad commit history. A small PR with 20 commits, each a half-thought, is harder to review than a small PR with 2 clean commits. Squash or rebase before opening. The PR size matters; so does commit cleanliness.
Mistake 3: Hiding changes in small PRs. I've seen developers split a refactor into 15 small PRs to avoid oversight, then bundle behavior changes into one of them. This is cargo cult small-PR practice. The point is clarity and quality, not gaming the system.
Mistake 4: Skipping context in the PR description. A small PR still needs a clear description. Why this change? What problem does it solve? What are the edge cases? In reviews, I find that small PRs with great descriptions get faster, better feedback than large PRs with "fixed bugs" as the summary.
Key takeaways
- Small pull requests get thorough review because reviewers can hold the entire change in working memory.
- Merge conflicts drop and feedback arrives faster when PRs are small, keeping the deployment pipeline active.
- Rollbacks from small PRs are clean and fast; rolling back a large PR is a production incident waiting to happen.
- Organize PRs by concern: separate refactoring, features, and cleanup into different PRs, each with a clear intent.
- Logical coherence matters more than hitting a line-count target; a 400-line feature PR is better than five artificial 80-line splits.
- Commit history within a small PR should be clean; squash before opening.
- Use the PR description to provide context; small PRs with clear intent get reviewed faster and more thoroughly.






Comments
Loading comments…