Skip to content

[Web Site Performance] Continuous CPU usage and memory leak on neetcode.io (Monaco Editor + PostHog/NgZone) #5787

@Ali-Al-Hadi-Al-Husseini

Description

@Ali-Al-Hadi-Al-Husseini

Description

The neetcode.io website exhibits significant performance degradation when left idle. Specifically, the site causes continuous high CPU usage and a steady memory leak, even when no user interaction is occurring.


Environment

  • Browser: Safari 26.4
  • OS: macOS Tahoe 26.4.1

Symptoms

  • High Energy Impact: Safari reports "High" energy usage immediately.
  • Memory Leak: The Safari Web Content process climbs to ~1.1 GB RAM within minutes on an idle page.
  • CPU Spikes: Sustained CPU usage between 37–142% while idle.

Image

memory usage I monitored on activity monitor
Image

Investigation & Technical Analysis

I collected profiler data and intercepted console traces . To assist in parsing the dense stack traces and identifying patterns within the RxJS observables, I used Claude to analyze the performance snapshots. This collaboration pinpointed two distinct causes:Analysis of the performance snapshots pinpointed two distinct causes:

1. Monaco Editor ResizeObserver Loop (Primary Cause)

The Monaco editor instance (8101.ceb05aeae98ec630.js) has a ResizeObserver on its container that fires in a tight loop. This schedules a requestAnimationFrame on every frame (~60/sec) even when the page is idle.

  • Trace: measureReferenceDomElement_recomputeOptionsfireendEmitViewEvents_scheduleRenderrequestAnimationFrame.
  • Error: Safari Console reports: ResizeObserver loop completed with undelivered notifications. (x19)

2. PostHog inside Angular's NgZone (Compounding Cause)

posthog-recorder.js registers a setInterval inside Angular's NgZone. Because it runs inside the zone, every flush cycle triggers Angular Change Detection. This prevents the application from reaching a "stable" state, compounding the composite/paint loop caused by the Monaco editor.


Suggested Fixes

1. Debounce Monaco ResizeObserver

Break the infinite loop by debouncing the layout call or triggering it only on actual window resize events.

const resizeObserver = new ResizeObserver(
  debounce(() => editor.layout(), 100)
);

2. Run PostHog Outside Angular Zone

Initialize the PostHog SDK outside of NgZone so that its internal timers and polling do not trigger unnecessary Change Detection cycles.

constructor(private ngZone: NgZone) {
  this.ngZone.runOutsideAngular(() => {
    posthog.init('YOUR_KEY', { ... });
  });
}

Supporting Evidence

Timeline Data: 1,763 GPU composite events recorded in 30 seconds (~117/sec) on an idle page.

Layout Thrashing: 21 forced layouts detected during the recording period.
unable to upload json file because of the large size > 150mb

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions