Under The Hood
frontend angular Angular 18+ (zoneless, signal-based CD)

Zoneless Angular: provideExperimentalZonelessChangeDetection()

Last updated
Prerequisites:
Angular change detection basics (OnPush, Default)
Understanding of signals and Zone.js
  • angular
  • zoneless
  • change-detection
  • signals
  • zonejs
  • reactivity

Read at your depth

The practical view

Angular traditionally uses Zone.js: it monkey-patches browser APIs (setTimeout, addEventListener, Promise) so any async activity triggers change detection over the whole component tree. Zoneless mode removes Zone.js entirely: provideExperimentalZonelessChangeDetection() tells Angular to detect changes only when signal-based state (or explicit markForCheck) actually changes. With zoneless, your app no longer loads zone.js, async operations don't cause full-tree checks, and OnPush components refresh only when their inputs or read signals change. In Angular 18+ you can also use provideZonelessChangeDetection(); the API stabilized in later versions.

The same idea in other frameworks

react equivalent
React's granular re-renders are similar in spirit; Angular's zoneless CD is closer to Solid's fine-grained invalidation.

Legacy vs modern

Zone.js full-tree CD vs zoneless signal-driven CD

With Zone.js, any async task checks the whole tree; with zoneless, only views reading changed signals are refreshed.

before → after
Zone.js + full-tree CD
@Component(...)
export class App {
  count = 0;
  ngOnInit() {
    setInterval(() => this.count++, 1000); // ❌ OLD: zone patches → full-tree CD every tick
  }
}
Zoneless + signal-driven CD
count = signal(0); // ✅ NEW: only views reading count() refresh
constructor() {
  setInterval(() => this.count.update(c => c + 1), 1000);
}

Interview gotchas

Context

Interviewers ask this to check whether you understand the implicit contract Zone.js provided — that ANY async activity refreshed the view — and what must become explicit without it.

The mechanical answer

Under Zone.js, setTimeout, Promise callbacks, DOM events, and HTTP responses all automatically mark the tree for CD. Zoneless removes that blanket trigger: if you mutate plain (non-signal) component state inside a setTimeout or a Promise callback without any signal involvement, the view will NOT refresh automatically. The fixes are: model state with signals (signal(), computed(), input()), use the async pipe for observables (which calls markForCheck internally on emission), or explicitly call ChangeDetectorRef.markForCheck()/detectChanges(). Also, code that relied on zone's error handling or that triggers CD from non-angular contexts (e.g. a standalone utility that mutates state) must now route through signal primitives. The build also drops zone.js, changing bundle size and polyfill assumptions.

Trap

The naive answer is 'nothing breaks, it's just faster'. The real trap is stale views: any async mutation of non-signal state silently stops updating the UI. Another trap: assuming the async pipe stops working — it works, but through its own notification path, not through zone. Candidates who enumerate the explicit notification paths (signals, async pipe, markForCheck) demonstrate real migration experience.

Under The Hood — a multi-depth technical interview hub.

Third-party licenses Press ⌘ K to search.