Pair Review
Review complex or high-risk changes together in real time, to catch design issues that async review misses.
Why
Some changes are too complex or too risky for async review — a database migration that affects every row, a refactor that touches the auth system, a new architectural pattern. Async review on a PR catches line-level issues (a missing null check, a style violation) but misses design-level issues (the migration locks the table for 10 minutes, the refactor breaks the auth contract, the pattern does not scale). Pair review — two developers looking at the code together in real time — catches these design issues because the conversation explores the trade-offs, the alternatives, and the edge cases that a PR comment cannot. It also builds shared understanding: both developers know why the decision was made, and the next person to touch the code is not starting from scratch. Pair review is not for every PR — it is for the 5% of PRs where the design matters more than the lines, and async review is insufficient.
When
For high-risk changes (migrations, auth, payments), for architectural decisions (new patterns, major refactors), and when the PR is too complex to review async (the reviewer cannot understand it from the diff).
How
1. Identify the PRs that need pair review - Database migrations that affect existing data - Auth or security changes - Payment or billing changes - New architectural patterns or major refactors - Any PR where the design matters more than the lines 2. Schedule a 30-60 minute review session - Share the screen (Zoom, Tuple, VS Code Live Share) - The author walks through the change, the reviewer asks questions - Focus on the design, not the lines — the linter handles the lines 3. Discuss the trade-offs - What alternatives were considered? Why was this one chosen? - What are the failure modes? How does the system degrade? - What are the performance characteristics? Will it scale? - What is the migration path? How do we roll back? 4. Document the decisions - Record the session (with consent) or take notes - Write an ADR (Architecture Decision Record) for the decision - Link the ADR in the PR so future readers understand the why 5. Follow up with async review for the details - Pair review catches the design issues - Async review catches the line-level issues - Use both — they are complementary, not alternatives
Example
// Scenario: a database migration that splits the users table // into users and user_profiles. This is high-risk because: // - It affects every row in the database // - It changes the schema that every query depends on // - A mistake means data loss or downtime // Async review would miss: // - Does the migration lock the table? For how long? // - What happens to queries that run during the migration? // - How do we roll back if something goes wrong? // - What is the deploy order — migration first, then code, or vice versa? // Pair review session (30 minutes): // Author: "I'm splitting users into users and user_profiles. // The migration adds the new table, copies the data, then drops // the old columns." // Reviewer: "How long does the copy take? We have 10M rows." // Author: "About 5 minutes. It locks the table." // Reviewer: "5 minutes of downtime is not acceptable. Can we do it // in batches to avoid the lock?" // Author: "Yes, I can copy in batches of 10,000 with a short lock // per batch. The total migration takes 30 minutes but the app // stays up." // Reviewer: "What about the queries that read from users during // the migration?" // Author: "I'll update the code to read from both tables during // the transition, then clean up after." // Reviewer: "Good. Let's write that as an ADR and link it in the PR." // ADR: 2024-01-15-split-users-table.md """ # Split users table into users and user_profiles ## Context The users table has 30 columns, most of which are profile data not needed for auth. Queries that only need auth data load unnecessary columns. ## Decision Split into users (auth: id, email, password) and user_profiles (profile: name, avatar, bio). Migrate in batches to avoid locking the table. Update queries to read from both during the transition. ## Consequences - Migration takes 30 minutes but the app stays up - Queries must be updated to join the two tables - Rollback: re-merge the tables (documented in the down migration) """ // PR description links the ADR: // "See ADR: docs/adr/2024-01-15-split-users-table.md"
Common Problems
- ⚠Pair-reviewing every PR — the team spends 15 hours a week in review sessions for PRs that could have been reviewed async in 15 minutes, and the process becomes a bottleneck.
- ⚠Pair review without documentation — the two developers reach a decision, but the reasoning is not written down, and the next developer to touch the code does not understand why the decision was made.
- ⚠Pair review that becomes the author explaining and the reviewer nodding — the reviewer does not ask hard questions, the design issues are not caught, and the session is a lecture rather than a review.
- ⚠Skipping pair review for a high-risk migration because "the PR is small" — the PR is 50 lines but it drops a column that 100 queries depend on, and the async review did not catch the dependency.
Best Practices
- ✓Use pair review for the 5% of PRs that are high-risk or design-heavy (migrations, auth, architecture), and async review for the 95% — pair review catches design issues, async catches line issues.
- ✓Document the decisions from pair review in an ADR (Architecture Decision Record) linked in the PR — the reasoning is preserved for the next developer, not lost in a Zoom call.
- ✓Both participants should ask questions and challenge the design — a pair review where only one person talks is a lecture, not a review, and the design issues are not caught.
- ✓Follow up the pair review with an async review for the line-level details — the pair review catches the design, the async review catches the implementation, and both are needed.