Introduction
Pull requests (PRs) are the lifeblood of code review, but when they grow from a single line fix to thousands of files and millions of lines, performance can suffer. This guide walks you through the strategies used to keep the Files changed tab fast and responsive, even for the largest PRs. By following these steps, you'll learn how to tackle rendering bottlenecks, memory bloat, and interaction latency.

What You Need
- A React-based codebase for diff rendering (like GitHub's Files changed tab)
- Performance profiling tools (e.g., Chrome DevTools, Lighthouse, INP measurements)
- Understanding of virtual scrolling and DOM virtualization concepts
- Access to a test environment with large PRs (e.g., 10,000+ files or 1M+ lines)
- Familiarity with React
keyprops, memoization, and component refactoring
Step-by-Step Improvement Process
Step 1: Measure Baseline Performance
Before making any changes, collect metrics for extreme cases. Use Chrome DevTools to capture JavaScript heap size, DOM node count, and Interaction to Next Paint (INP) scores. For example, baseline measurements showed JS heap >1 GB, DOM nodes >400,000, and high INP. Document these for comparison.
Step 2: Optimize Diff-Line Components
Focus on the core diff rendering. Reduce unnecessary re-renders by memoizing components with React.memo and using stable key props. Avoid inlining functions in JSX. Optimize the diff-line component to only re-render when its content actually changes. This keeps medium and large reviews fast while preserving native find-in-page functionality.
Step 3: Implement Graceful Degradation with Virtualization
For the largest PRs, adopt virtual scrolling (e.g., using libraries like react-window or react-virtuoso). Only render the lines visible in the viewport plus a small overscan buffer. This limits DOM nodes to a few hundred instead of hundreds of thousands. Ensure that the diff still loads progressively while interactions remain responsive. Use a threshold (e.g., PR size > 1000 files) to switch to virtualized rendering automatically.
Step 4: Invest in Foundational Rendering Components
Refactor shared components (e.g., diff decorations, file headers, syntax highlighting) to avoid expensive calculations on every render. Use useMemo and useCallback to cache derived data. Batch DOM updates with ReactDOM.flushSync where appropriate. These improvements compound across all PR sizes, making even small PRs snappier.

Step 5: Monitor and Tune Memory Consumption
Reduce memory footprint by unmounting off-screen components in virtualized views. Avoid storing large objects in React state or refs. Use WeakMap for caching computed styles. After optimization, verify that JS heap stays under 200 MB for extreme PRs and DOM nodes drop below 5,000.
Step 6: Validate INP Scores and User Feedback
Re-measure INP scores to ensure they fall below the 'good' threshold (e.g., 200 ms). Test on low-end devices and throttled networks. Collect user feedback on perceived lag. Iterate on any remaining bottlenecks.
Tips for Success
- Start with profiling: Always baseline before optimizing—without data, you'll guess wrong.
- Avoid silver bullets: No single technique works for all sizes; combine strategies as shown.
- Test with real-world PRs: Synthetic benchmarks may miss edge cases like syntax highlighting or images in diffs.
- Prioritize responsiveness over completeness: Graceful degredation is better than frozen UI.
- Use internal anchor links to jump to each step: Step 1, Step 2, etc.
- Document trade-offs: Virtualization may break find-in-page; you might need a hybrid approach or a fallback.