github

The uphill climb of making diff lines performant (opens in new tab)

GitHub rebuilt the pull request Files changed experience to keep diff reviews responsive across everything from tiny fixes to massive changes. The core conclusion is that no single optimization solves performance at scale; instead, targeted rendering improvements, virtualization, and simpler components must work together. Early results show that even small reductions in DOM size can have substantial effects on memory usage and interaction latency in large pull requests.

Performance Challenges at GitHub’s Scale

  • Pull requests may contain thousands of files and millions of lines.
  • In extreme cases, the old experience reached:
    • More than 1 GB of JavaScript heap usage
    • Over 400,000 DOM nodes
    • Unacceptably high Interaction to Next Paint (INP) scores
  • Large reviews became sluggish or nearly unusable, despite the experience remaining fast for most smaller pull requests.

A Strategy Based on Pull Request Size

GitHub concluded that different pull request sizes require different performance strategies:

  • Optimize diff-line components so medium and large reviews remain fast without losing expected browser behavior, such as native find-in-page.
  • Use virtualization for the largest reviews, rendering only the content currently needed to preserve responsiveness and stability.
  • Improve foundational components and rendering, allowing performance gains to benefit every pull request size.

Problems with the Original Diff Architecture

The first React implementation made each diff line unnecessarily expensive:

  • Unified view used roughly 10 DOM elements per line; split view used about 15, before syntax highlighting added more <span> elements.
  • Each unified diff line typically involved at least eight React components, while split view involved at least 13.
  • Additional states—such as comments, hover, and focus—could add still more components.
  • Small components often registered five or six React event handlers each, resulting in 20 or more handlers per line.
  • These costs multiplied across thousands of lines, increasing JavaScript heap usage and worsening INP.
  • The component-heavy design was initially reasonable when React was introduced, but proved unsustainable for unbounded data sets.

Incremental Improvements in the New Design

GitHub’s second version focused on simplification and removing unnecessary structure:

  • Reduced state, JavaScript, React components, and DOM elements.
  • Removed redundant <code> tags from line-number cells.
  • Eliminating just two nodes per line saves approximately 20,000 DOM nodes across 10,000 lines.
  • The example demonstrates how seemingly minor changes compound into meaningful improvements at large scale.

The practical lesson is that performant large-scale interfaces require layered optimizations: simplify every repeated element, reduce per-item overhead, and use virtualization when rendering everything at once is no longer viable.