Managing Pull Requests Effectively
A pull request (PR) is more than just a mechanism for merging code — it is a conversation, a quality gate, and a record of why a change was made. Teams that manage PRs well ship faster, with fewer bugs, and with a healthier codebase than teams that treat PRs as an annoying formality. This guide covers both sides of the PR: how to author an excellent PR and how to be an effective reviewer.
As an Author: Small, Focused PRs
The single most impactful thing you can do as a PR author is keep your PRs small. A 50-line PR gets reviewed in 10 minutes; a 1,000-line PR gets "looks good to me" after a cursory glance.
PR size | Lines changed | Typical review time | Defect detection rate |
|---|---|---|---|
Tiny | 1–50 | 5–10 minutes | Excellent |
Small | 50–200 | 15–30 minutes | Good |
Medium | 200–400 | 30–60 minutes | Acceptable |
Large | 400–800 | 1–2 hours | Poor — reviewers skim |
Huge | 800+ | Often not reviewed properly | Very poor |
How to keep PRs small:
One logical change per PR: "Add user search endpoint" not "Add user search + redesign the user list page + update documentation".
Split large features into a vertical slice: database schema → API endpoint → UI → tests — each as a separate PR.
If you find unrelated bugs while working, open a separate PR for the fix rather than bundling it in.
Avoid "while I'm in here" reformatting — it obscures the real change in the diff.
PR Description Template
A well-written description dramatically reduces back-and-forth. Reviewers should be able to understand what changed, why, and how to verify it without having to ask.
PR description template
## What
Add a search endpoint for users: `GET /api/v1/users?q=<query>`
## Why
Product requirement: users need to find teammates by name or email
from the invite dialog. Closes #142.
## How to test
1. Start the server: `npm run dev`
2. Open Postman or run:
`curl "http://localhost:3000/api/v1/users?q=alice"`
3. Expect: JSON array of users whose name or email contains "alice"
4. Test empty query: `?q=` should return all users (paginated)
5. Test no match: `?q=xyzxyz` should return `{"users":[]}`
## Screenshots
Before: No search capability in invite dialog
After: [screenshot of search results appearing]
## Notes for reviewers
- The search is case-insensitive and uses ILIKE on Postgres.
- Rate-limited to 10 req/s per user to prevent abuse.
- Pagination is handled by the existing `?page=` and `?per_page=` params.
## Checklist
- [x] Tests added (see user.search.test.ts)
- [x] Documentation updated (API.md)
- [x] No console.log left
- [ ] Performance tested on large dataset (TODO in #143)Self-Review Before Requesting Review
Before tagging anyone as a reviewer, read your own diff as if you were the reviewer. This takes five minutes and consistently catches:
Forgotten
console.logor debug comments.Hardcoded values that should be environment variables.
Missing error handling in new code paths.
Tests that are incomplete or copy-pasted incorrectly.
Files that shouldn't be in the PR (lock files, IDE configs).
Merge artifacts or accidentally reverted changes.
Review your own diff before requesting review
# Full diff of your PR against the base branch git diff main...HEAD # Or per-commit git log main..HEAD --oneline # abc1234 feat(api): add user search endpoint # def5678 test(api): add tests for user search git show abc1234 git show def5678
Assigning Reviewers and Labels
Assign at least one reviewer who is familiar with the affected code area.
For critical changes, assign two reviewers or the team lead.
Use labels to signal status:
ready for review,work in progress,needs rebase,blocked.Use the GitHub CODEOWNERS file to automatically assign reviewers based on which files changed.
If you need a quick gut-check before a full review, mention it in Slack — don't abuse the formal review queue.
.github/CODEOWNERS — automatic reviewer assignment
# Each line assigns a pattern to reviewers # Last matching rule wins # Global owners — review everything * @org/core-team # Frontend changes src/components/** @alice @bob src/styles/** @alice # Backend API src/api/** @charlie @diana src/database/** @charlie # Infrastructure *.yml @devops-team Dockerfile @devops-team terraform/** @devops-team # Documentation docs/** @tech-writers *.md @tech-writers
Handling Review Feedback
Feedback type | How to respond | When to use each |
|---|---|---|
Simple fix | New commit on the branch | Small, targeted changes |
Amend last commit | git commit --amend | Only if the change is tiny and PR not yet reviewed |
Interactive rebase | git rebase -i to edit earlier commits | Cleanup BEFORE requesting re-review, with reviewer consent |
Discussion/disagreement | Reply in thread, then implement if resolved | When you need clarity before making changes |
Prefer adding new commits over amending during active review. When you amend or force-push, reviewers lose the context of what changed since their last review.
Responding to feedback with a new commit
# After making the requested change: git add src/api/users.ts git commit -m "fix(api): add input sanitization per review feedback" git push origin feature/user-search # The PR page now shows the new commit # Reviewer can see exactly what changed since their review
Keeping Your PR in Sync with the Base Branch
When main moves forward while your PR is open, your branch becomes out of date. You need to update it before merging.
Update your PR branch via rebase (preferred)
git fetch origin git rebase origin/main # Successfully rebased and updated refs/heads/feature/user-search. # If there are conflicts: # 1. Fix the conflicted files # 2. git add <resolved-files> # 3. git rebase --continue # (git rebase --abort to cancel if needed) # Force push after rebase git push --force-with-lease origin feature/user-search
Update strategy | History result | When to prefer |
|---|---|---|
Rebase on main | Linear history, cleaner | Default for most PRs |
Merge main into branch | Creates a merge commit | When rebase conflicts are too complex |
Resolving Conflicts in a PR
Resolve conflicts during rebase
git fetch origin git rebase origin/main # CONFLICT (content): Merge conflict in src/api/users.ts # error: could not apply abc1234... feat(api): add user search endpoint # Step 1: see what is in conflict git status # rebase in progress; onto def5678 # You are currently rebasing branch 'feature/user-search'... # # Unmerged paths: # both modified: src/api/users.ts # Step 2: open the file and resolve the conflict markers # <<<<<<< HEAD (this is the main branch version) # ======= (separator) # >>>>>>> your commit # Step 3: after editing to the desired state git add src/api/users.ts git rebase --continue # [detached HEAD e4f0a12] feat(api): add user search endpoint # Step 4: push git push --force-with-lease origin feature/user-search
Draft PRs for Early Feedback
A Draft PR signals that the work is not ready for formal review yet. Use drafts to:
Share work-in-progress for early architectural feedback before investing hours in the full implementation.
Show a proof-of-concept and ask "does this approach make sense?".
Keep long-running feature branches visible without triggering review pressure.
Run CI checks on a branch that is not ready to merge.
Open a draft PR with gh CLI
gh pr create \ --title "feat(api): user search [WIP]" \ --body "Draft — seeking architectural feedback before finishing implementation" \ --draft # Convert to ready for review when done: gh pr ready
Auto-Close Issues with "Fixes #123"
GitHub automatically closes linked issues when a PR that references them is merged. Use these keywords in the PR description or a commit message:
Keywords that auto-close issues on merge
# In PR description or commit message body: Closes #142 Fixes #142 Resolves #142 # Multiple issues: Closes #142, closes #167 # Cross-repo: Closes org/other-repo#89
As a Reviewer: Best Practices
Be timely: aim to review PRs assigned to you within one business day. Blocking a colleague for three days is a real productivity cost.
Understand context first: read the PR description, look at the linked issue, understand the "why" before diving into the diff.
Comment with suggestions, not commands: "What do you think about extracting this to a helper function?" is better than "Extract this".
Distinguish blocking vs non-blocking: prefix non-blocking suggestions with
nit:oroptional:so the author knows which comments are required for approval.Approve when satisfied: don't leave PRs in limbo. If your concerns are addressed, approve.
Be kind: remember there is a human being on the other side who spent time on this code.
Example review comment styles
# Blocking (must fix before merge) The password is logged here on line 42. This is a security issue — please remove this log statement before merging. # Suggestion (author's choice) nit: This condition could be written as a single ternary: const label = isActive ? 'Active' : 'Inactive' # Question (seeking understanding, not requiring change) optional: Could we use an enum here instead of a string literal? Just wondering if there was a reason for the string approach. # Praise (also valid review content!) Nice refactor — this is much easier to read than the old version.