Repository navigation
perf: avoid O(N^2) accumulator spread in initHiddenPaths - #310
Merged
Merged
Conversation
initHiddenPaths used `{ ...acc, [path]: 1 }` inside reduce, which
rebuilds the accumulator on every iteration. For N flat nodes this is
O(N^2) — copying ~50M properties for ~10K nodes and blocking the main
thread for 3-4s on initial mount with large payloads (related to leezng#138).
Replace the spread with in-place assignment (`acc[path] = 1`). reduce
accumulators are private — mutation here is safe and O(N).
Measured locally on a 1.9MB payload (~10K flat nodes): cold mount 4s -> ~50ms.
Author
|
@leezng Can you review this PR please? |
leezng
approved these changes
Apr 29, 2026
Owner
|
@ibreathebsb Thanks! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
initHiddenPaths(Tree/index.tsx) builds the hidden-paths map with a reduce that spreads the accumulator on every iteration:For N flat nodes this allocates a fresh object per iteration and copies every previously-added key — O(N²) total, with N(N-1)/2 property copies.
initHiddenPathsis called synchronously insidesetup(line 112) when constructingstate.hiddenPaths, so this cost lands on the cold-mount path.For small JSON it's invisible. For large payloads it dominates: on a real-world 1.9MB payload (~10K flat container nodes) this single line blocks the main thread for ~3–4 seconds during initial render. Profiler confirms the time is spent inside the reduce, not in
jsonFlattenor DOM work.This appears to be the same root cause behind the long-standing #138.
Fix
reduceaccumulators are private — mutating in place is safe. One-line change from spread to assignment makes it O(N):Behavior is identical. The final
as Record<string, 1>cast is preserved.Measurements
Same 1.9MB payload, same
deep=1, same machine:initHiddenPathstime<VueJsonPretty>Smaller payloads see no perceptible change (already fast).
Risk
Closes #138 (or contributes substantially toward it).