SYSTEM_DESIGN
System Design: Code Review System
Design a code review system that supports inline comments, multi-round reviews, automated checks, and large-diff handling for engineering teams at scale. Covers diff computation, comment threading, and review state machines.
Requirements
Functional Requirements:
- View diffs between source and target branches line-by-line
- Post inline comments on specific lines in specific files
- Support threaded discussions (replies to comments)
- Request changes, approve, or comment without approval
- Required reviewer assignments and approval gating before merge
- Automated status checks (CI passing, coverage thresholds, lint)
Non-Functional Requirements:
- Diff rendering under 500ms for PRs up to 10,000 line changes
- Support 1 million active PRs concurrently
- 100,000 code review operations/sec (view, comment, approve)
- Comment persistence: never lose a posted comment
- 99.99% availability
Scale Estimation
With 1 million active PRs: average PR size is 500 lines changed across 10 files. Diff storage: 500 lines × 200 bytes/line = 100 KB per PR diff; 1 million PRs = 100 GB of diff data (small). Comments: average 10 comments per PR = 10 million comments in 1 million PRs; at 500 bytes per comment: 5 GB. The bottleneck is not storage but computation: rendering a 10,000-line diff with syntax highlighting and inline comments requires significant CPU per request. Pre-computing and caching diffs is essential. At 100,000 code review operations/sec across 1 million active PRs, ~10% of PRs are actively accessed at any time.
High-Level Architecture
The system has three main subsystems: Diff Service (computes and renders code diffs), Review Service (manages PR state, comments, and approvals), and Notification Service (sends review-related notifications). A PR Repository (relational DB) stores structured PR metadata. Diff computation runs on-demand (with caching) using a diff engine that calls into the Git storage layer (Gitaly). Comment data is stored in PostgreSQL with a denormalized structure for fast per-PR retrieval.
PR creation flow: developer opens PR (source branch → target branch). The system fetches the merge-base commit (common ancestor), computes the diff (via git diff merge_base source) using Gitaly's CommitDiff RPC, stores the diff result in a cache (Redis + S3 for large diffs), and creates PR metadata in PostgreSQL. Required reviewers are assigned based on CODEOWNERS rules (file-to-reviewer mapping stored as a repository file). The PR is published to a review queue, and reviewers receive notifications (email, Slack, in-app).
Diff rendering uses a three-step pipeline: (1) raw diff parsing (unified diff format → structured patch objects); (2) syntax highlighting (Linguist language detection + Prism/Pygments for token-level highlighting); (3) HTML rendering (line-by-line table with line numbers, additions in green, deletions in red, inline comment threads embedded at the relevant line). Syntax-highlighted output is cached per (commit SHA, file path) since the same file version may appear in multiple PRs. The rendered HTML for the full diff is cached per (PR ID, latest commit SHA) with a 10-minute TTL.
Core Components
Diff Engine
Diff computation uses Myers' diff algorithm (O(ND) time, O(D) space, where D is the number of differences) or its patience diff variant (preferred for code: aligns on unique lines, producing more human-readable diffs). For large files, diff computation is parallelized: each file diff is computed independently, and all file diffs for a PR are computed in parallel across multiple Diff Service workers. The raw diff output (unified diff format) is stored as a compressed blob (gzip, 10:1 compression for typical code diffs) in S3, keyed by (base_sha, head_sha, file_path). A Redis cache stores the most recent 10,000 diff computations for instant retrieval on repeated PR views.
Comment Threading & Positioning
Inline comments are anchored to a specific (file_path, diff_line_number) in the diff. The challenge: when the PR author pushes new commits, the diff changes — previously-posted comments must be repositioned to the correct lines in the new diff. Line positioning uses a diff mapping algorithm: for each comment position in the old diff, the algorithm traces the same lines through the new diff (using the diff's line change records) to compute the new position. If the commented line was modified or deleted in the new commit, the comment is marked as "outdated" (shown collapsed with a note that the code has changed). Comments on unmodified context lines maintain their position exactly.
Review State Machine
A PR follows a state machine: open → review_requested → changes_requested / approved → merged / closed. Multiple reviewers can be in different states simultaneously (reviewer A approved, reviewer B requested changes). Merge is gated by: (1) minimum required approvals (e.g., 2 approvals); (2) no outstanding "request changes" reviews from required reviewers; (3) all required CI status checks passing. Branch protection rules enforce these gates atomically in the merge operation (checked and locked before git merge executes). Dismissal of stale reviews (auto-dismiss approvals when new commits are pushed) is configurable per repository.
Database Design
Pull requests table: (id, repo_id, number, title, source_branch, target_branch, base_sha, head_sha, state, author_id, created_at, merged_at, merge_sha). Review requests: (pr_id, reviewer_id, state: requested/approved/changes_requested, submitted_at). Comments: (id, pr_id, commit_id, file_path, diff_line, body, author_id, created_at, resolved: bool, parent_comment_id for threading). All tables are indexed on (pr_id) for fast PR-level queries. Comments table has a GIN index on body for comment search. Diff blobs are stored in S3 (not in PostgreSQL) to keep the database lightweight. A separate comment_reactions table (emoji reactions) is stored in Redis for low-durability, high-frequency updates.
API Design
Scaling & Bottlenecks
Large monorepo PRs are the hardest scaling challenge. A single PR touching 1,000 files with 50,000 lines changed generates a diff payload of 10 MB+ of rendered HTML. Sending this to the browser at once is untenable. Virtualized diff rendering (render only the visible viewport in the browser, loading additional hunks on scroll) limits DOM size to the visible content. Diff data is paginated by file: the first 20 files are rendered immediately; additional files load on demand as the user scrolls. File-level diff caching (per file path + commit SHA) ensures subsequent PR views for files that haven't changed are served from cache.
Comment volume scales with PR activity. A high-profile PR (e.g., a major framework change) may accumulate 1,000+ comments across multiple review rounds. Loading all comments on PR open would require 1,000 database rows per page load, which is fast (PostgreSQL handles this in <10ms with an index scan on pr_id). The challenge is real-time updates: if 50 reviewers are simultaneously viewing and commenting, each new comment must be pushed to all 50 viewers. WebSocket connections (one per browser tab) receive push notifications for new comments, maintaining near-real-time synchronization without polling.
Key Trade-offs
- Server-side vs. client-side diff rendering: Server-side syntax highlighting produces consistent output and offloads browser CPU but creates server-side rendering load; client-side (JS syntax highlighters like Prism) scales infinitely but may produce inconsistent highlighting and adds JS bundle size
- Persistent vs. on-demand diff computation: Pre-computing diffs on PR creation ensures fast first-load but wastes compute for PRs that are never viewed; on-demand computation wastes CPU on repeated views without caching
- Anchor by line number vs. by content hash: Anchoring comments to line numbers is simple but brittle when code is inserted above the commented line; anchoring by content hash (the commented line's text) is more robust but fails when the exact line content changes
- Blocking vs. non-blocking required reviews: Blocking merges until all required reviewers approve prevents bad code from shipping but creates bottlenecks when reviewers are unavailable; non-blocking (optional approvals) moves faster but relies on team discipline
GO DEEPER
Master this topic in our 12-week cohort
Our Advanced System Design cohort covers this and 11 other deep-dive topics with live sessions, assignments, and expert feedback.