Skip to the solution
HardFrontend9 min read

What is NgZone and when do you run outside Angular?

242 views

The problem

Question presented to candidate: "We have an Angular app that still uses Zone.js, and profiling shows change detection firing hundreds of times a second while a chart animates. What is NgZone, why is this happening, and how would runOutsideAngular help? Does any of this apply to a new Angular 22 app?"

What a strong answer should cover:

  • NgZone is Angular's wrapper around a Zone.js zone named "angular"; every async task started inside it is tracked, and when the zone settles (onMicrotaskEmpty) Angular runs change detection.
  • Hot async work inside the zone (requestAnimationFrame, mousemove, setInterval, WebSocket messages) therefore causes a run per task.
  • runOutsideAngular(fn) starts work in the parent (root) zone, so its tasks do not trigger runs; run(fn) re-enters the Angular zone when the UI must update.
  • Also: NgZone.isInAngularZone(), onStable/onUnstable, and provideZoneChangeDetection({ eventCoalescing: true }).
  • Since v21 apps are zoneless by default and inject a no-op NgZone; Zone.js is opt-in via provideZoneChangeDetection() plus the polyfill. With OnPush (v22 default), even re-entering the zone does not refresh a plain field; signals do.

Clarifying questions expected:

  • "Is this app on provideZoneChangeDetection() or already zoneless?"
  • "Is the hot work coming from our code or from a third-party library that schedules its own timers?"

Code / implementation expected: Yes β€” move a high-frequency loop outside the zone and re-enter with zone.run() for the final UI update.

  • ngzone
  • performance

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

9 min read Β· 9 sections

Read the code

Angular100 timers inside vs outside the Angular zone (app opted into Zone.js)
import {
  ApplicationConfig, Component, NgZone, inject, provideZoneChangeDetection, signal,
} from "@angular/core";

@Component({
  selector: "app-root",
  template: `
    <button id="inside" (click)="burst(false)">100 timers inside the zone</button>
    <button id="outside" (click)="burst(true)">100 timers outside the zone</button>
    <p>finished bursts: {{ done() }} | plain field: {{ plain }}</p>
  `,
})
export class AppComponent {
  private zone = inject(NgZone);
  done = signal(0);
  plain = 0;
  private turns = 0;

  constructor() {
    // onMicrotaskEmpty fires each time the Angular zone settles, which is what
    // makes Zone-based Angular run change detection.
    this.zone.onMicrotaskEmpty.subscribe(() => this.turns++);
    console.log("NgZone in use:", this.zone instanceof NgZone, "| in Angular zone:", NgZone.isInAngularZone());
  }

  burst(outside: boolean) {
    // Let the click's own turn finish, then count only what the timers cause.
    setTimeout(() => {
      this.turns = 0;
      const schedule = () => { for (let i = 0; i < 100; i++) setTimeout(() => this.plain++); };
      outside ? this.zone.runOutsideAngular(schedule) : this.zone.run(schedule);
      setTimeout(() => {
        console.log(`${outside ? "outside" : "inside "}: zone turns that could trigger change detection = ${this.turns}`);
        // Coming back in: a signal tells Angular what changed. Plain field writes alone would not
        // refresh this OnPush (v22 default) view, even inside zone.run().
        this.zone.run(() => this.done.update(n => n + 1));
      }, 50);
    });
  }
}

// Zone.js-based change detection is opt-in since v21 (and zone.js must be loaded as a polyfill).
export const appConfig: ApplicationConfig = {
  providers: [provideZoneChangeDetection({ eventCoalescing: true })],
};

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 116 of 121 in Angular

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Angular questions
Up nextHard

What is the difference between fakeAsync, tick, and waitForAsync in Angular unit tests?

Continue