Home/Blog/Pull Request Best Practices: Code Review, Templates, and Merge Strategies
Software Engineering

Pull Request Best Practices: Code Review, Templates, and Merge Strategies

Master pull request workflows with best practices for code review, PR templates, merge strategies, automated checks, and team collaboration patterns.

By Inventive HQ Team
Pull Request Best Practices: Code Review, Templates, and Merge Strategies

Pull requests are where code becomes software. They're the gateway between individual work and shared codebases, the checkpoint for quality, and the primary collaboration touchpoint for development teams. This guide covers how to create, review, and merge pull requests effectively.

Anatomy of a Great Pull Request

┌─────────────────────────────────────────────────────────────┐
│                     PULL REQUEST                             │
├─────────────────────────────────────────────────────────────┤
│  TITLE: [Type] Brief description of change                   │
│         feat: Add user authentication flow                   │
├─────────────────────────────────────────────────────────────┤
│  DESCRIPTION:                                                │
│  ├── What changed and WHY                                   │
│  ├── How to test                                            │
│  ├── Screenshots (if UI)                                    │
│  ├── Related issues: Fixes #123                             │
│  └── Checklist                                              │
├─────────────────────────────────────────────────────────────┤
│  METADATA:                                                   │
│  ├── Reviewers: @teammate                                   │
│  ├── Labels: feature, needs-review                          │
│  ├── Milestone: v2.0                                        │
│  └── Project: Q1 Sprint                                     │
├─────────────────────────────────────────────────────────────┤
│  CHECKS:                                                     │
│  ├── ✓ CI/CD Pipeline                                       │
│  ├── ✓ Code Coverage                                        │
│  ├── ✓ Security Scan                                        │
│  └── ✓ Required Reviewers                                   │
└─────────────────────────────────────────────────────────────┘

PR Title Conventions

Use consistent prefixes for scannable history:

PrefixUsageExample
feat:New featurefeat: Add dark mode toggle
fix:Bug fixfix: Resolve login timeout issue
docs:Documentationdocs: Update API reference
refactor:Code restructurerefactor: Extract auth logic to service
test:Test changestest: Add unit tests for user service
chore:Maintenancechore: Update dependencies
perf:Performanceperf: Optimize database queries
style:Formattingstyle: Fix linting errors

Pull Request Templates

Create .github/PULL_REQUEST_TEMPLATE.md in your repository:

## Description
<!-- What changed and why? Link to design docs if applicable -->

## Type of Change
- [ ] Bug fix (non-breaking change fixing an issue)
- [ ] New feature (non-breaking change adding functionality)
- [ ] Breaking change (fix or feature causing existing functionality to change)
- [ ] Documentation update

## How Has This Been Tested?
<!-- Describe the tests you ran and how to reproduce -->
- [ ] Unit tests
- [ ] Integration tests
- [ ] Manual testing

## Screenshots (if applicable)
<!-- Add screenshots for UI changes -->

## Checklist
- [ ] My code follows the project's style guidelines
- [ ] I have performed a self-review
- [ ] I have added tests that prove my fix/feature works
- [ ] New and existing unit tests pass locally
- [ ] I have updated documentation as needed
- [ ] My changes generate no new warnings

## Related Issues
<!-- Link related issues: Fixes #123, Relates to #456 -->

Multiple Templates for Different PR Types

Create .github/PULL_REQUEST_TEMPLATE/ directory:

.github/
└── PULL_REQUEST_TEMPLATE/
    ├── feature.md
    ├── bugfix.md
    ├── hotfix.md
    └── documentation.md

Contributors select the appropriate template when creating PRs.

Code Review Best Practices

For Authors

Before requesting review:

┌─────────────────────────────────────────────────────────────┐
│                   SELF-REVIEW CHECKLIST                      │
├─────────────────────────────────────────────────────────────┤
│  □ Review your own diff in "Files changed" tab              │
│  □ Remove debug code, console.logs, commented code          │
│  □ Check for typos in strings, comments, variable names     │
│  □ Verify tests pass locally                                │
│  □ Run linter and fix issues                                │
│  □ Add comments on complex sections to guide reviewers      │
│  □ Ensure PR description explains the WHY                   │
│  □ Link related issues                                      │
│  □ Request specific reviewers who know this code            │
└─────────────────────────────────────────────────────────────┘

Responding to feedback:

  • Respond to every comment (even just "Done" or "Good catch")
  • Explain your reasoning if you disagree
  • Batch related changes into single commits
  • Re-request review after addressing feedback

For Reviewers

Review mindset:

┌─────────────────────────────────────────────────────────────┐
│                    REVIEW PRIORITIES                         │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│   1. CORRECTNESS (Must fix)                                 │
│      └── Does it work? Logic errors? Edge cases?            │
│                                                              │
│   2. SECURITY (Must fix)                                    │
│      └── Injection risks? Auth issues? Data exposure?       │
│                                                              │
│   3. DESIGN (Should discuss)                                │
│      └── Architecture fit? Maintainability? Patterns?       │
│                                                              │
│   4. READABILITY (Nice to have)                             │
│      └── Clear names? Good comments? Understandable?        │
│                                                              │
│   5. STYLE (Automate instead)                               │
│      └── Formatting? Linting rules? Let tools handle it     │
│                                                              │
└─────────────────────────────────────────────────────────────┘

Effective review comments:

<!-- Bad: Vague -->
This is wrong.

<!-- Good: Specific with suggestion -->
This will throw a NullPointerException if `user` is null (line 42).
Consider adding a null check:
```java
if (user != null && user.isActive()) {

Use camelCase.

Our codebase uses camelCase for consistency with the existing API client. (Better: configure linter to catch this automatically)


**Review comment prefixes:**

| Prefix | Meaning | Action Required |
|--------|---------|-----------------|
| `blocking:` | Must fix before merge | Yes |
| `suggestion:` | Consider this approach | Optional |
| `question:` | Need clarification | Response needed |
| `nit:` | Minor nitpick | Optional |
| `praise:` | Good work! | None |

### Review Workflow

┌─────────────────────────────────────────────────────────────┐ │ REVIEW LIFECYCLE │ ├─────────────────────────────────────────────────────────────┤ │ │ │ Author creates PR │ │ │ │ │ ▼ │ │ ┌─────────────────┐ │ │ │ Request Review │───► Reviewers notified │ │ └────────┬────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────┐ │ │ │ Review Period │───► Comment, Approve, or │ │ └────────┬────────┘ Request Changes │ │ │ │ │ ┌───────┴───────┐ │ │ ▼ ▼ │ │ Approved Changes Requested │ │ │ │ │ │ │ ▼ │ │ │ Author addresses │ │ │ feedback │ │ │ │ │ │ │ ▼ │ │ │ Re-request review │ │ │ │ │ │ └───────┬───────┘ │ │ ▼ │ │ ┌─────────────────┐ │ │ │ MERGE │ │ │ └─────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘


## Merge Strategies

### Comparison

┌─────────────────────────────────────────────────────────────┐ │ MERGE STRATEGIES │ ├─────────────────────────────────────────────────────────────┤ │ │ │ MERGE COMMIT │ │ main: A───B───C───────M─── │ │ \ / │ │ feature: D───E───F │ │ │ │ • Preserves all commits │ │ • Shows branch history │ │ • Creates merge commit (M) │ │ │ ├─────────────────────────────────────────────────────────────┤ │ │ │ SQUASH AND MERGE │ │ main: A───B───C───S─── │ │ │ │ • Combines all PR commits into one (S) │ │ • Clean, linear main history │ │ • Loses individual commit granularity │ │ │ ├─────────────────────────────────────────────────────────────┤ │ │ │ REBASE AND MERGE │ │ main: A───B───C───D'───E'───F'─── │ │ │ │ • Replays commits on top of main │ │ • Linear history, no merge commits │ │ • Rewrites commit hashes │ │ │ └─────────────────────────────────────────────────────────────┘


### When to Use Each

| Strategy | Best For | Avoid When |
|----------|----------|------------|
| **Merge commit** | Feature branches with meaningful commit history | Main has many merge commits already |
| **Squash and merge** | Feature branches with messy commits (WIP, fixup) | Commits should be preserved individually |
| **Rebase and merge** | Clean branches with atomic commits | Commits are already on main (double commits) |

### Configuring Default Strategy

In GitHub repository settings:

1. Go to **Settings > General**
2. Scroll to **Pull Requests**
3. Configure allowed merge methods:
   - Allow merge commits
   - Allow squash merging ✓ (default for most teams)
   - Allow rebase merging

You can also set a default merge button behavior.

## Automated Checks

### Required Status Checks

Configure in **Settings > Branches > Branch protection rules**:

```yaml
# Example CI workflow for PR checks
name: PR Checks
on:
  pull_request:
    branches: [main]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run linter
        run: npm run lint

  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run tests
        run: npm test

  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Build
        run: npm run build

  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run security scan
        uses: snyk/actions/node@master
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}

PR Size Warnings

Automatically warn on large PRs:

name: PR Size Check
on: pull_request

jobs:
  size-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Check PR Size
        run: |
          ADDITIONS=$(gh pr view ${{ github.event.pull_request.number }} --json additions -q '.additions')
          DELETIONS=$(gh pr view ${{ github.event.pull_request.number }} --json deletions -q '.deletions')
          TOTAL=$((ADDITIONS + DELETIONS))

          if [ $TOTAL -gt 500 ]; then
            echo "::warning::Large PR detected ($TOTAL lines changed). Consider splitting into smaller PRs."
          fi
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Auto-labeling

Automatically label PRs based on files changed:

# .github/labeler.yml
frontend:
  - 'src/components/**'
  - 'src/pages/**'
  - '**/*.css'

backend:
  - 'src/api/**'
  - 'src/services/**'

documentation:
  - '**/*.md'
  - 'docs/**'

tests:
  - '**/*.test.ts'
  - '**/*.spec.ts'
# .github/workflows/labeler.yml
name: Labeler
on: pull_request

jobs:
  label:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/labeler@v5
        with:
          repo-token: ${{ secrets.GITHUB_TOKEN }}

CODEOWNERS for Automatic Reviewers

Create .github/CODEOWNERS:

# Default owners for everything
* @org/core-team

# Frontend code
/src/components/ @org/frontend-team
/src/pages/ @org/frontend-team
*.css @org/frontend-team
*.tsx @org/frontend-team

# Backend code
/src/api/ @org/backend-team
/src/services/ @org/backend-team

# Infrastructure
/terraform/ @org/devops-team
/.github/workflows/ @org/devops-team
Dockerfile @org/devops-team

# Security-sensitive code requires security team review
/src/auth/ @org/security-team @org/backend-team
/src/encryption/ @org/security-team

# Documentation
/docs/ @org/docs-team
*.md @org/docs-team

# Package management
package.json @org/core-team
package-lock.json @org/core-team

Enable CODEOWNERS enforcement:

  1. Settings > Branches > Branch protection
  2. Enable "Require review from Code Owners"

Team Collaboration Patterns

Handling Stacked PRs

When features depend on other PRs not yet merged:

┌─────────────────────────────────────────────────────────────┐
│                    STACKED PRs                               │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│   main                                                       │
│     │                                                        │
│     └──► feature/auth-base (PR #1)                          │
│              │                                               │
│              └──► feature/auth-oauth (PR #2)                │
│                      │                                       │
│                      └──► feature/auth-ui (PR #3)           │
│                                                              │
│   Workflow:                                                  │
│   1. Create PR #1 against main                              │
│   2. Create PR #2 against feature/auth-base                 │
│   3. Create PR #3 against feature/auth-oauth                │
│   4. Review and merge PR #1                                 │
│   5. Retarget PR #2 to main (GitHub auto-updates)           │
│   6. Review and merge PR #2                                 │
│   7. Retarget PR #3 to main                                 │
│   8. Review and merge PR #3                                 │
│                                                              │
└─────────────────────────────────────────────────────────────┘

Draft PRs for Early Feedback

Use draft PRs when:

  • You want early feedback on approach
  • CI needs to run during development
  • You're sharing progress with stakeholders
  • You need to collaborate on complex changes
# Create draft PR with GitHub CLI
gh pr create --draft --title "feat: Add auth" --body "WIP: Authentication system"

# Mark ready for review when done
gh pr ready

Keeping PRs Updated

# Option 1: Merge main into your branch
git checkout feature-branch
git fetch origin
git merge origin/main
git push

# Option 2: Rebase onto main (cleaner but requires force push)
git checkout feature-branch
git fetch origin
git rebase origin/main
git push --force-with-lease

PR Metrics and Team Health

Track these metrics to improve PR workflows:

MetricTargetWhy It Matters
Time to first review< 4 hoursFast feedback keeps developers productive
Review cycles1-2 roundsMore indicates unclear requirements
PR size< 400 LOCLarge PRs get worse reviews
Time to merge< 2 daysLong waits indicate bottlenecks
Review participationBalancedAvoid relying on one reviewer

Measuring with GitHub Insights

# Using GitHub CLI to analyze PR metrics
gh pr list --state merged --json number,createdAt,mergedAt,additions,deletions \
  --jq '.[] | "\(.number): \(.additions + .deletions) lines, \(((.mergedAt | fromdate) - (.createdAt | fromdate)) / 3600 | floor) hours"'

Quick Reference

PR Author Checklist

□ Self-review completed
□ Tests added/updated
□ Documentation updated
□ PR title follows conventions
□ Description explains what and why
□ Screenshots for UI changes
□ Related issues linked
□ Appropriate reviewers assigned

Reviewer Checklist

□ Understand the context (read description first)
□ Check for correctness and edge cases
□ Look for security issues
□ Verify tests are meaningful
□ Assess maintainability
□ Provide actionable feedback
□ Approve, request changes, or comment

Frequently Asked Questions

Find answers to common questions

Keep PRs under 400 lines of code changes when possible. Studies show review effectiveness drops significantly above this threshold. If a feature requires more changes, split into multiple PRs: one for data models, one for business logic, one for UI. Reviewers can focus better, and smaller PRs merge faster with fewer conflicts.

Merge commit preserves all PR commits plus a merge commit—shows full history but can be noisy. Squash and merge combines all PR commits into one—clean main history, loses granular commits. Rebase and merge replays PR commits onto main—linear history without merge commits but rewrites commit hashes. Most teams prefer squash for feature branches.

Include: what changed and why (not just what—reviewers can see the diff), how to test it (steps or commands), screenshots for UI changes, related issues (Fixes #123), breaking changes or migration steps, and checklist items for reviewer focus. Use a PR template to standardize these sections across your team.

One required reviewer is minimum, two is common for important code. More than two rarely adds value and slows merging. Consider: one domain expert who knows the codebase area, and one generalist for fresh perspective. For critical code (security, payments), require specific team members as reviewers using CODEOWNERS.

Yes, always self-review first. Check the Files Changed tab as a reviewer would. You'll catch obvious issues, typos, debug code, and missing tests before wasting reviewers' time. Add comments on your own PR explaining complex sections or noting areas you're uncertain about to guide reviewers.

Discuss objectively, not personally—critique code, not the author. If stuck, move to synchronous discussion (call or meeting) rather than long comment threads. Defer to code owners or team leads for final decisions. Document the decision in the PR for future reference. Remember the goal is better code, not winning arguments.

Essential: linting, type checking, unit tests, security scanning, build verification. Recommended: integration tests, code coverage thresholds, dependency vulnerability scanning, PR size warnings. Optional: performance benchmarks, visual regression tests. Block merging until critical checks pass using branch protection rules.

First, read the PR description and understand the goal. Review commits individually if they tell a logical story. Start with test files to understand expected behavior. Review data models and interfaces before implementation. Use GitHub's file filtering to focus on one area at a time. Don't try to review everything in one session.

Draft PRs signal work-in-progress that's not ready for full review. Use them for early feedback on approach before investing in polish, sharing progress with team leads, running CI checks during development, and collaborative troubleshooting. Convert to ready when you want formal review. Reviewers know not to nitpick drafts.

Use merge (git merge main into your branch) to preserve your commit history—creates merge commits but is safer. Use rebase (git rebase main) for cleaner history but requires force push if already pushed. Most teams prefer merge for simplicity. Update before final review to catch conflicts early and ensure CI runs against latest main.

Engineering Excellence for Your Business

Our engineers build systems that scale. Clean architecture, comprehensive testing, and security-first development.