October 3, 2025 By WBG Development Team
wildermine performance technical optimization

36% Memory Reduction: Phase 4 Task 1 Results

Replacing JSON.stringify() with stable array references eliminated 24.7 MB of peak memory growth and stabilized garbage collection patterns

36% Memory Reduction: Phase 4 Task 1 Results

36% Memory Reduction: Phase 4 Task 1 Results

We replaced JSON.stringify() with stable array references in the level editor’s ghost highlight system. The result: 36% reduction in peak memory usage (24.7 MB saved) with smoother and more stable garbage collection patterns.

Part of the Phase 4 Frontend & Backend Optimization series. Read the Task 1 Baseline Analysis first.


The Fix: useStableArray Hook

The problem was simple: JSON.stringify() creates a new string object on every mouse move, breaking React’s memoization. The solution was equally simple: cache array references and only update when contents actually change.

// client/src/hooks/useStableArray.ts
function useStableArray<T>(arr: T[] | null | undefined): T[] {
  const ref = useRef<T[]>(arr || []);

  // Only update ref if array contents changed
  if (!arraysEqual(ref.current, arr || [])) {
    ref.current = arr || [];
  }

  return ref.current;
}

Applied to 3 locations:

  • GameplayLevelCanvas.tsx:308-312
  • TestLevelCanvas.tsx:290-294
  • useLivePreviewBuildableHighlights.ts:32-42

Before:

useMemo(() => {
  // expensive calculation
}, [JSON.stringify(ghostCellPositions || [])])

After:

const stableGhostPositions = useStableArray(ghostCellPositions);

useMemo(() => {
  // expensive calculation
}, [stableGhostPositions])

Results Comparison

MetricBeforeAfterImprovement
Peak memory68.4 MB43.7 MB-24.7 MB (36%)
Memory range35.1-68.4 MB35.7-43.7 MBNarrower, more stable
String allocations19.8 MB (41%)22.4 MB (44%)*Spread more evenly
GC spikesInfrequent but disruptiveMore frequent but gentlerLess noticeable pauses

String allocation percentage is similar, but in the “After” case allocations are steadier and spread over time instead of building into large spikes.

Memory Graph Comparison

Before (Baseline): Baseline performance showing large range between net heap memory

  • Large sawtooth pattern with infrequent, aggressive GC cycles
  • Orange line (retained memory) rises and does not come back down, suggesting references remain alive unnecessarily

After (Optimized): Optimized performance showing smoother patterns

  • Heap stays in a much narrower band
  • GC runs more frequently, but cleans up smaller amounts — less disruptive for interactivity
  • Orange line spikes a couple of times but then stays flat at a low level, showing healthier retention

What Changed (And What Didn’t)

✅ Improved

Memory stability: The memory graph shows much smoother allocation patterns without aggressive spikes.

GC pressure: More frequent but gentler garbage collection cycles—less disruptive for interactivity.

Render efficiency: Flamegraph bars are noticeably thinner, confirming memoization now works correctly.

📊 Similar (As Expected)

Commit counts: Still ~550-600 commits during the 10-second hover test. This is expected—mouse move events still trigger renders, but each render is cheaper because memoization works.

String allocations: Percentage remains around 41-44%. The difference is that allocations are more controlled and spread over time rather than spiking aggressively.

🎯 Real Impact

No perceived lag: The level editor feels smooth during hover interactions, especially on lower-end devices.

Better memory headroom: Provides breathing room for other game systems without triggering aggressive GC.

Foundation for future work: Stable array references set the pattern for Tasks 2-4.


Phase 4 Task 1 Summary:

  • 36% peak memory reduction (24.7 MB saved)
  • Smoother memory patterns (fewer GC spikes)
  • Memoization working (thinner flamegraph bars)
  • Zero functional regressions
  • Foundation for Tasks 2-4

Implementation and measurements completed using Claude Code with document-driven development approach.

WBG Logo

Written by WBG Development Team

Part of the passionate team at Wrinkled Brain Games, creating innovative gaming experiences.