virtualization

4 posts

figma

Improving Performance in the Layers Panel | Figma Blog (opens in new tab)

Figma rebuilt its layers panel to handle files containing tens of thousands of layers. The old architecture recomputed too much data too often, slowing both panel interactions and broader editor operations. A two-pass computation model and cached derived properties now make some interactions 30–50% faster. ## Why the Original Architecture Slowed Down - Figma files are trees of nodes with properties and children. - The panel previously built a large JavaScript object in one recursive pass. - Every expanded node—and often all of its descendants—had its display data recomputed after changes. - This created two problems: - Data was computed for hundreds of thousands of nodes even though only 20–30 rows were visible. - Small changes, such as expanding a node, triggered broad recomputation because incremental results were rarely cached. ## Two-Pass Computation - The first pass computes only the ordered list of row IDs shown in the panel. - Determining that order still requires handling complex rules, including: - Reversed child ordering in autolayout frames. - Node types such as widgets and FigJam stickies that hide children. - Fixed and scrolling headers that divide prototype-frame children. - Sticky top-level frames and components. - The second pass gathers display data—names, icons, lock and visibility state, and selection state—only for rows inside the visible window. - This makes windowing effective: previously, Figma computed data for off-screen rows even though they were not rendered. ## Caching Derived Data - Figma introduced its “derived properties” platform primitive to avoid recomputing unchanged data. - Nodes store mutable fields, while other values are calculated from those fields and related properties. - For example, a node’s absolute position can be derived from its parent’s absolute position and its relative position: ```text Self.AbsolutePosition = Parent.AbsolutePosition + Self.RelativePosition ``` - Derived properties: - Track their dependencies through an optimized dependency graph. - Support different caching strategies balancing speed and memory. - Are lazy by default, computing values only when they are read. - Because the layers panel is itself a tree, this dependency-aware system lets Figma update only affected rows while keeping stable portions cached. Figma’s results show the value of combining virtualization with incremental, dependency-based computation: large hierarchical interfaces can remain responsive when they calculate only visible data and preserve everything that has not changed.

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.

figma

Version Control: Three Ways Figma Explored Horizontal Scrolling | Figma Blog (opens in new tab)

Figma’s request for horizontal scrolling in the Layers panel exposed significant design and engineering complexity. The panel contains interactive, virtualized layers whose names and nesting change dynamically, so scrolling could easily obscure hierarchy or disrupt users’ mental models. Through three unshipped prototypes, the team learned that preserving context and preventing accidental actions mattered more than applying seemingly simple scrolling behaviors. ## Why Horizontal Scrolling Was Difficult - Nested layers can extend beyond the panel, requiring users to move horizontally to see their full hierarchy. - Layers are interactive: users can hide, lock, collapse, and expand them while scrolling. - Virtualization renders only visible layers for performance, meaning newly visible rows may have different text lengths. - The design needed to communicate that more content existed without making the hierarchy harder to understand. - Code-based prototyping with JavaScript, HTML, CSS, and React helped the team test behavior across thousands of layers and complex interactions. ## Version One: Indicators at the Panel Edges - The team explored showing icons at the top-left and bottom-right edges when layers moved out of view. - This symmetrical treatment was intended to help users understand their position in the hierarchy. - Engineering challenges arose because edge icons needed opaque backgrounds that covered layer text while scrolling. - Layer rows did not have enough positional knowledge to determine which areas should be covered or revealed. - The design also created a ragged edge where partially visible layer names ended, introducing visual clutter. - The experiment demonstrated that a symmetrical solution could create new problems, particularly around unused white space. ## Version Two: Automatically Scrolling to Selected Layers - Another prototype automatically scrolled the Layers panel to center a layer selected on the canvas. - Although this made the selected layer easy to find, it often moved the panel both vertically and horizontally. - Testers found the behavior disorienting because parent layers and surrounding context disappeared. - Figma compared the experience to a map suddenly jumping to another location while driving. - The team concluded that users need to retain their mental model of a layer’s name, location, and relationship to parent components. ## Version Three: Renaming Layers While Scrolling - Horizontal scrolling created ambiguity when a user began editing a layer name and then scrolled to another layer. - One possible behavior was to save the edited text automatically when scrolling away. - The team rejected this because scrolling was not a strong enough signal that the user intended to confirm the rename. - The issue highlighted the need to avoid turning navigation actions into irreversible edits. Figma’s experiments show that horizontal scrolling in a dynamic hierarchy is not merely a visual or mechanical feature. The best solution must preserve orientation, context, and user control while handling virtualization and interactive layer states.

discord

Supercharging Discord Mobile: Our Journey to a Faster App (opens in new tab)

Discord’s mobile performance work focuses on making power-user workflows faster and more reliable, especially on lower-end Android devices. The team improved memory usage and frame rates through virtualization, native UI optimizations, recycling, and better media formats. When React Native could not meet performance needs, Discord chose targeted native implementations despite the added maintenance burden. ## React Native and Performance Priorities - Discord uses React for desktop and React Native for mobile. - Android moved to React Native in 2022 after improvements in device capabilities and the Hermes JavaScript engine. - The transition initially affected startup performance, but median startup times were cut in half during 2023. - Recent work targets heavily used features and users with large servers, busy chats, and extensive emoji collections. ## Virtualized Server List - The old server list kept every server view and icon—including animated icons—in memory. - The rewritten virtualized list retains only information for servers currently visible on screen. - The change also enabled improved drag-and-drop animations. - Users with more than 100 servers saw: - 14% lower startup memory usage - 10% faster overall startup time ## Native Chat Performance Improvements - Discord profiled its already-native chat list and found opportunities to improve loading, scrolling, and memory use. - Application Components and Media Mosaics now use recycling mechanisms. - Infrequently used elements, including spoilers and upload overlays, are loaded lazily. - Message containers and reactions were made faster to measure. - Recycling pools are pre-filled with common chat elements before a chat is opened. - Results included up to 60% fewer slow frames and approximately 12% lower chat-list memory usage. ## Rebuilding the Android Emoji Picker - Large servers can provide hundreds of custom emojis, many of them animated. - The previous React Native picker produced blank frames—and sometimes crashes—on budget Android devices during rapid scrolling. - Discord rebuilt the picker natively for Android rather than applying incremental optimizations. - Stress tests involving emojis from 200 servers eliminated blank frames and delivered smooth scrolling on low-end hardware. - Discord accepted the cost of maintaining separate Android and iOS implementations, while planning to reassess the decision with React Native’s New Architecture. ## Animated WebP Emojis - Discord’s media proxy added animated WebP support. - Replacing GIFs with WebP enabled animated emojis to render at up to 60 frames per second on low-end devices. - The team plans to publish more details about its animated-image improvements. ## Eliminating ChannelList Blanking - Discord initially replaced its homegrown React Native FastList with Shopify’s FlashList. - FlashList simplified development because components did not require precisely accurate dimensions. - In practice, FlashList blanked more frequently on low-end Android devices. - Discord restored and refactored FastList, eliminating blanking even on a $50 phone. Discord’s approach combines broad React Native development with native solutions where performance is critical. Virtualization, recycling, lazy loading, and efficient image formats provide general improvements, while platform-specific rewrites remain justified for demanding experiences such as emoji browsing.