Rearchitecting Figma's Layers Panel
The layers panel is a central part of the Figma experience — a hierarchical blueprint of every element in a file. Originally built nearly seven years ago for smaller files, the panel's architecture has struggled to keep pace with today's massive, feature-rich documents. Files with tens of thousands of layers caused sluggish interactions, and the performance burden sometimes spilled over, slowing down other editor operations like dragging or typing. To restore smoothness, especially in the largest files, we rebuilt the panel's underlying architecture.
At the heart of the problem were two architectural weaknesses:
- Computing too much: The panel generated data for every expanded node, even though a typical screen displays only 20–30 rows.
- Computing too often: There was little incremental caching. A single change, such as expanding a node, triggered a full recomputation for all expanded nodes.
Each issue required a distinct solution. The first was addressed by changing the order of data assembly; the second by adopting a platform-level caching primitive.
Two-Pass Computation
To avoid needless work, the team split the data-gathering phase in two. In the first pass, we retrieve only the ordered list of row IDs that make up the panel. Even this list is tricky to compute correctly. Children of auto-layout frames appear in reverse order, certain node types (like widgets and FigJam stickies) hide their children, prototyping frames can have fixed headers that split children into subsections, and top-level frames are sticky.

With IDs in hand, the second pass computes actual node data — names, icons, lock state, visibility, selection — but only for the rows that are windowed, i.e., visible on screen. While the old architecture also used windowing, its single-pass assembly meant it still gathered full data for nodes that would never be rendered. By deferring data computation until after IDs are known, we now compute this data for a few dozen rows instead of potentially hundreds of thousands.
Caching Derived Data
The second issue — redundant recomputation — was solved using derived properties. Each Figma node has mutable fields, but many useful properties aren't stored directly. A node's absolute position, for example, must be derived from its relative position to its parent. Derived properties formalize this: they are computed values declared like spreadsheet formulas that reference other cells.
The system offers several advantages: every derived property tracks its dependencies in an optimized graph, there are multiple caching policies balancing speed and memory, and computations are lazy — they only run when read. For the layers panel, this means a change to the tree invalidates only the relevant parts of the derived-property graph, not everything in sight.
Take computing the ordered list of rows. We can express a node's children as a derived property:
Self.OrderedChildren =
If(Self.Expanded)
Children.FlatMap(Child => Child.OrderedChildren)
Else
[]
This property is invalidated only if the node's expansion state changes or its children's OrderedChildren change. The lazy nature of the property also helps: if a node is never expanded, its OrderedChildren is never requested and never computed.
Managing Memory Use
Caching adds memory pressure, so we were careful about implementation strategy. A naive caching implementation would store the entire list of descendant IDs at every level of the tree. Since each level stores all its descendants, the total memory cost follows a triangular-number sum: 1 + 2 + … + n equals n(n + 1)/2. In deep trees, this O(n²) usage could hit tens of megabytes — unacceptable.
The solution was a recursive list, similar to a rope data structure. Instead of copying sequences, this approach composes them via pointers, maximizing structural sharing across tree levels. As a result, memory use became strictly proportional to the number of unique nodes — O(n) — with only a minor cost from extra pointer traversals during reads. This reduced memory usage by up to 99% compared with the initial prototype.
Frame A: [B, C, D]
Frame B: [C, D]
Frame C: [D]
Frame D: []
Performance Impact
The combined effects of two-pass computation and derived-property caching yielded dramatic improvements. Critical panel interactions such as expanding or collapsing rows and toggling visibility or lock state became roughly 30–50% faster in some of the largest and most complex files.
The benefits extended beyond the layers panel. Cutting needless computation improved overall rendering performance in the design editor, leading to fewer slow frames and higher frame rates. Operations that previously felt sluggish — typing, dragging, selecting colors in complex files — now run consistently smoothly.
The work doesn't stop here. We continue to push on performance across Figma.



