Practical Guide • Updated February 2026

Better Pull Requests
The Complete Guide

Learn how to write pull requests that get reviewed faster and approved more often. Includes code examples, PR checklists, description templates, and best practices for GitHub, GitLab, and Bitbucket.

What Makes a Great Pull Request?

A great pull request isn't just about working code. It's about making your reviewer's job easier. When reviewers can quickly understand what changed and why, they approve faster and give better feedback.

Research from Google and Microsoft shows that PRs under 400 lines get reviewed 2x faster and have 50% fewer defects than larger PRs. But size is just one factor. Great PRs also have:

Clear Context

The description explains why the change exists, not just what changed. Links to issues, tickets, or design docs give full background.

Focused Scope

One logical change per PR. No mixing refactoring with feature work, no unrelated cleanup, no scope creep.

Thorough Testing

Evidence that the code works: test results, screenshots, demo videos, or step-by-step reproduction instructions.

Respectful of Reviewer Time

Self-reviewed first, automated checks pass, no obvious mistakes, and formatted consistently with the codebase.

Key Takeaway

Great PRs optimize for reviewer experience. Every minute you spend making your PR easier to review saves time across all your reviewers. On a team of 5, that's 5x ROI on your documentation time.

Anatomy of a PR That Gets Approved Fast

Fast-approved PRs share a common structure. Here's the anatomy of a PR that reviewers love:

1

Descriptive Title (50 chars max)

Use imperative mood: "Add user authentication" not "Added authentication". Start with a verb. Follow team conventions (e.g., feat:, fix:).

feat: Add OAuth2 authentication with Google
Updated some files for auth stuff
2

Context Section

Explain why this change exists. Link to the issue/ticket. Describe the problem being solved.

## Context
Users can't sign in with Google (Issue #1234).
We need OAuth2 support to reduce signup friction.
3

Changes Section

Bullet list of what changed. Focus on what, not how (code shows how).

## Changes
- Add GoogleAuthProvider service
- Implement OAuth2 callback handler
- Add "Sign in with Google" button to login page
- Store OAuth tokens in secure httpOnly cookies
4

Testing Section

Prove it works. Screenshots, test results, or step-by-step instructions.

## Testing
1. Click "Sign in with Google" on /login
2. Authorize app in Google consent screen
3. Verify redirect to /dashboard with user data
4. All unit tests pass (see CI results)
5

Notes Section (Optional)

Breaking changes, migration steps, deployment notes, or areas needing extra attention.

## Notes
- Requires GOOGLE_CLIENT_ID env var (see .env.example)
- No breaking changes to existing auth flow

Pro Tip

Use a PR template to enforce this structure. GitHub, GitLab, and Bitbucket all support templates in .github/pull_request_template.md. It takes 5 minutes to set up and saves hours of back-and-forth.

Writing PR Descriptions That Reviewers Love

Your PR description is the first thing reviewers see. A good description answers questions before they're asked, while a bad description wastes everyone's time.

The Bad PR Description

Title: Update code
Fixed some bugs and added a feature.

What's wrong? No context, vague changes, no testing info. Reviewers have to read the entire diff to figure out what's happening.

The Good PR Description

feat: Add rate limiting to API endpoints
## Context
Our API endpoints have no rate limiting, making them vulnerable
to abuse. This implements token bucket rate limiting per user.
Closes #567
## Changes
- Add RateLimiter middleware using redis
- Apply 100 req/min limit to all /api/* routes
- Return 429 status with Retry-After header
- Add rate limit metrics to dashboard
## Testing
- Unit tests for RateLimiter class (see test/rate-limiter.test.ts)
- Integration test: verified 101st request returns 429
- Load tested with 1000 req/s, no performance degradation
## Notes
- Requires REDIS_URL env var in production
- Rate limits configurable via RATE_LIMIT_MAX env var

Why it's better: Clear problem statement, specific changes, testing evidence, deployment notes. Reviewers know exactly what to expect.

Common Mistake

Don't just copy commit messages into the PR description. Commit messages are implementation details. PR descriptions are for humans reviewing the overall change. Focus on the why and what, not the how.

Keeping PRs Small and Focused

The single most impactful change you can make is to keep PRs small. Data from Google, Microsoft, and Cisco shows that smaller PRs are reviewed faster, have fewer defects, and get better feedback.

The Numbers

400
Max lines of code changes
Optimal size for quick review
2x
Faster review time
PRs under 400 lines vs larger
50%
Fewer defects
Small PRs vs large PRs

How to Keep PRs Small

1. Split by Functionality

Break large features into incremental PRs: first the data model, then the API, then the UI. Each PR should be independently deployable or use feature flags.

PR 1: Add User table schema → PR 2: Add User API endpoints → PR 3: Add User UI

2. Separate Refactoring from Features

Never mix refactoring with new features. First PR: refactor existing code. Second PR: add new feature using refactored code. This makes both PRs easier to review.

PR 1: Extract auth logic into AuthService → PR 2: Add OAuth using AuthService

3. Use Feature Flags

Ship incomplete features behind flags. This lets you merge small PRs continuously without exposing work-in-progress to users.

if (featureFlags.newDashboard) { /* new code */ } else { /* old code */ }

4. Exclude Generated Code

Don't include auto-generated files (migrations, build artifacts, lock files) in your line count. Focus reviewer attention on hand-written code.

Real-World Strategy

When you start a feature branch, plan your PR sequence upfront. Write down the logical steps and commit boundaries. This prevents the dreaded "giant PR" at the end. You'll merge faster and get better feedback along the way.

Code Examples: Good vs Bad PRs

Let's look at real code changes. These examples show common mistakes and how to fix them.

Example 1: Mixed Concerns

Bad: Mixing refactoring with feature work
// PR: Add user profile feature
// 📁 src/auth/AuthService.ts
class AuthService {
-  login(username, password) {
+  async login(credentials: LoginCredentials) {
     // ... refactored auth logic
  }
+  async updateProfile(userId, profile) {
+    // ... new profile feature
+  }
}

Problem: Reviewer has to mentally separate refactoring (login signature change) from new feature (updateProfile). This slows down review and increases error risk.

Good: Separate PRs for refactoring and features
// PR 1: Refactor AuthService to async/await
class AuthService {
-  login(username, password) {
+  async login(credentials: LoginCredentials) {
     // ... refactored auth logic
  }
}
// PR 2: Add user profile update feature
class AuthService {
  async login(credentials: LoginCredentials) {
    // ... existing code
  }
+  async updateProfile(userId, profile) {
+    // ... new profile feature
+  }
}

Why it's better: Each PR has one clear purpose. Reviewers can approve the refactoring quickly, then focus on the new feature logic separately.

Example 2: Vague Commit Messages

Bad: Unhelpful commit history
abc1234 fix stuff
def5678 update code
ghi9012 oops
jkl3456 final changes

Problem: No context for what changed or why. Reviewers can't use commit history to understand the evolution of the PR.

Good: Meaningful commit messages
abc1234 feat: Add UserProfile model and database migration
def5678 feat: Implement updateProfile API endpoint
ghi9012 feat: Add profile update form to dashboard
jkl3456 test: Add unit tests for profile update flow

Why it's better: Each commit has a clear purpose. Reviewers can understand the implementation sequence. Use interactive rebase to clean up "fix typo" commits before review.

Example 3: Missing Context in Code

Bad: Magic numbers and unclear logic
if (user.loginAttempts > 5) {
  user.locked = true;
  user.lockUntil = Date.now() + 1800000;
}

Problem: Why 5 attempts? What is 1800000? Reviewers have to calculate (30 minutes) and guess business rules.

Good: Named constants with context
// Lock account after too many failed login attempts to prevent brute force
const MAX_LOGIN_ATTEMPTS = 5;
const ACCOUNT_LOCK_DURATION_MS = 30 * 60 * 1000; // 30 minutes

if (user.loginAttempts > MAX_LOGIN_ATTEMPTS) {
  user.locked = true;
  user.lockUntil = Date.now() + ACCOUNT_LOCK_DURATION_MS;
}

Why it's better: Business logic is self-documenting. Reviewers immediately understand the security policy without mental math.

Self-Review Checklist

Before requesting review, check your own PR for these issues:

  • No magic numbers — use named constants
  • No mixing refactoring with features
  • Commit messages describe why, not just what
  • No commented-out code or debug logs
  • Code follows team style guide

PR Templates and Checklists

Templates enforce consistency and reduce cognitive load. Here's a battle-tested PR template you can use with GitHub, GitLab, or Bitbucket.

PR Description Template

## Context <!-- Why does this PR exist? Link to issue/ticket. --> Closes # ## Changes <!-- What changed? Bullet list, focus on what not how. --> - - - ## Testing <!-- How was this tested? Screenshots, test output, repro steps. --> - ## Notes <!-- Breaking changes? Migration steps? Deployment requirements? --> - [ ] No breaking changes - [ ] Updated documentation (if needed) - [ ] Added tests (if needed)

Pre-Submit Checklist

Use this checklist before clicking "Create Pull Request":

Self-reviewed the diff
Read your own changes as if you're the reviewer
All automated checks pass
Tests, linting, type checking, build
PR is under 400 lines
If larger, split into multiple PRs
Description answers what, why, how tested
Don't make reviewers guess
No unrelated changes
Each PR has one purpose
Commit messages are meaningful
Squashed "fix typo" commits
Screenshots/videos for UI changes
Show before/after visuals
Breaking changes documented
Migration steps included

Implementation Guide: Save the template as .github/pull_request_template.md (GitHub), .gitlab/merge_request_templates/default.md (GitLab), or configure in Bitbucket repository settings.

Automating PR Quality Checks

Great teams automate quality checks so reviewers can focus on logic and architecture. Here are the must-have automations for every PR:

1. CI/CD Pipeline

Run tests, linting, type checking, and build on every PR. Block merging if checks fail. Use GitHub Actions, GitLab CI, or Bitbucket Pipelines.

# .github/workflows/pr-checks.yml
name: PR Checks
on: [pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm test
      - run: npm run lint
      - run: npm run type-check
      - run: npm run build

2. Code Coverage Enforcement

Require test coverage on new code. Tools like Codecov or Coveralls comment on PRs with coverage delta. Block PRs that decrease coverage.

✓ Coverage: 87.3% (+2.1%) — All checks passed

3. AI Code Review

Use AI to catch bugs, security issues, and code smells before human review. Tools like Git AutoReview run Claude, Gemini, and GPT on every PR with human approval before posting.

4. Automated Security Scanning

Scan for vulnerabilities in dependencies (npm audit, Snyk, Dependabot) and code (CodeQL, Semgrep). Block PRs with critical security issues.

5. PR Size Checks

Automatically flag PRs over 400 lines. Use GitHub Actions or Danger.js to comment on large PRs: "This PR is large (847 lines). Consider splitting for faster review."

Automation Philosophy

Automate the boring stuff (syntax, formatting, tests) so human reviewers can focus on the interesting stuff (architecture, edge cases, maintainability). Every automated check is one less thing reviewers have to manually catch.

Common PR Anti-Patterns

Avoid these mistakes that slow down reviews and frustrate your team:

The Kitchen Sink PR

Multiple unrelated changes in one PR: new feature + refactoring + dependency updates + bug fixes. Reviewers don't know where to start.

Fix: One logical change per PR. Split into feature PR, refactoring PR, dependency PR.

The "Fix Review Comments" Commit

Responding to feedback with commits like "fix review comments" or "address feedback". Reviewers have to diff the diff to see what changed.

Fix: Use meaningful commit messages even for fixes: "Add null check to user validation". Or amend original commit if it hasn't been reviewed yet.

The Force-Push Rewriter

Force-pushing to rewrite history during active review. Reviewers lose their place, comments get orphaned, and everyone is confused.

Fix: Only force-push before review starts. During review, add new commits. Clean up history after approval but before merge.

The No-Description PR

Empty description or just "See title". Reviewers have to reverse-engineer your intent from code alone.

Fix: Use a PR template. Fill out Context, Changes, Testing, Notes. Takes 2 minutes, saves hours.

The "Works on My Machine" PR

No testing evidence, failing CI, or "tests pass locally" comments. Reviewers can't trust the code works.

Fix: Include test output, screenshots, or reproduction steps. Fix CI before requesting review. "Works on my machine" is not evidence.

The Merge-Without-Approval PR

Self-approving or merging before reviewers finish. Defeats the entire purpose of code review.

Fix: Require at least one approval before merge. Use branch protection rules. Be patient — reviews take time.

How AI Can Improve Your PRs

AI code review tools can catch issues before human reviewers see your PR. But not all AI tools are created equal. Human-in-the-loop approval prevents AI mistakes from cluttering your pull requests.

What AI Can Catch

Security Vulnerabilities

SQL injection, XSS, insecure dependencies, exposed secrets, authentication bypasses

Code Quality Issues

Unused variables, duplicate code, overly complex functions, inconsistent naming

Logic Errors

Off-by-one errors, null pointer exceptions, race conditions, incorrect API usage

Best Practice Violations

Missing error handling, improper resource cleanup, synchronous blocking in async code

Git AutoReview: Human-in-the-Loop AI

Git AutoReview runs Claude, Gemini, and GPT on your PR, but you approve every suggestion before it posts to your pull request. This prevents the 29-45% of AI hallucinations from reaching reviewers.

Self-review with AI assistance: Run AI review before creating PR, fix issues yourself
Multi-model comparison: See suggestions from Claude, Gemini, and GPT side-by-side
Approve good suggestions: Only publish AI comments that add value
Works with GitHub, GitLab, Bitbucket: Supports all major Git platforms

Try Git AutoReview Free

Install from VS Code Marketplace. Run AI review on your next PR. Approve the good suggestions, reject the hallucinations. No credit card required.

Install Git AutoReview Free

Frequently Asked Questions

How big should a pull request be?

Keep PRs under 400 lines of code changes. Research shows PRs under 400 lines get reviewed 2x faster and have 50% fewer defects than larger PRs. If your change is bigger, split it into multiple PRs with logical boundaries.

What should I include in a PR description?

Every PR description should answer: What changed? Why did it change? How was it tested? Include links to related issues, screenshots for UI changes, and migration steps for breaking changes. Use the template: Context, Changes, Testing, and Notes.

How to handle large PRs that can't be split?

For genuinely large changes (refactoring, migrations), provide extra documentation: architecture diagrams, step-by-step explanations, file-by-file guides, and schedule a synchronous review meeting. Consider draft PRs for early feedback before marking ready for review.

Should I squash commits before creating a PR?

Keep meaningful commit history during development, but clean it up before final review. Use interactive rebase to squash 'fix typo' and 'oops' commits, but keep logical steps separate. Many teams squash all commits on merge anyway, so prioritize PR-level clarity over commit-level perfection.

How do I write a good PR title?

Use the imperative mood ('Add feature' not 'Added feature'), start with a verb, keep it under 50 characters, and follow your team's convention (e.g., 'feat:', 'fix:', 'refactor:'). Good: 'Add user authentication with OAuth'. Bad: 'Updated some files'.

What's the best way to respond to PR feedback?

Respond to every comment (even if just 'Done' or thumbs up), push fixes in a new commit (don't force-push until approved), explain your reasoning when you disagree, and mark conversations as resolved only after the reviewer approves. Thank reviewers for their time.

How long should I wait before pinging reviewers?

Depends on team culture, but 24-48 hours is reasonable for most teams. If urgent, mention it in the PR description and ping in Slack/Teams. Set up PR notifications so reviewers see new requests immediately. Use draft PRs if you're not ready for review yet.

Should I use PR templates?

Yes. PR templates ensure consistency, reduce cognitive load for reviewers, and prevent missing critical information like testing steps or breaking changes. GitHub, GitLab, and Bitbucket all support PR templates in .github/pull_request_template.md.

Write Better Pull Requests Today

Use Git AutoReview to catch issues before human review. Human-in-the-loop AI with Claude, Gemini, and GPT. Works with GitHub, GitLab, and Bitbucket. Free to install.

Related Guides