How Do You Prevent Memory Leaks in Angular? All Real-World Unsubscribe Patterns Explained

LowEasyAngular
Preparing for interviews?

Use guided tracks for structured prep, then practice company-specific question sets when you want targeted interview coverage.

Quick Answer

Memory leaks in Angular almost always come from forgotten subscriptions, long-lived streams, or global listeners. The correct fixes include using the async pipe, takeUntilDestroyed/DestroyRef, takeUntil with a Subject, and understanding which streams complete automatically and which never do.

Answer

Core idea

Angular itself does not magically clean up your RxJS subscriptions. If a stream does not complete by itself and you keep a subscription alive after the component is destroyed, you leak memory, CPU, and sometimes network activity. Most Angular memory leaks come from forgotten subscriptions or global listeners.

Source type

Does it auto-complete?

Needs manual unsubscribe?

Why

HttpClient request

✅ Yes

❌ No

HTTP observables complete after one response.

ActivatedRoute params, valueChanges, Subjects

❌ No

✅ Yes

They are long-lived streams tied to app lifetime.

interval, fromEvent, WebSocket

❌ No

✅ Yes

They never complete unless you stop them.

async pipe

❌ No

Angular unsubscribes automatically on destroy.

Which subscriptions leak if you forget to clean them up?

Pattern #1: Use the async pipe (best default)

The async pipe automatically subscribes and unsubscribes when the component is destroyed. It also integrates perfectly with OnPush change detection.

TYPESCRIPT
@Component({
  selector: 'app-users',
  template: `
    <ul>
      <li *ngFor="let u of users$ | async">{{ u.name }}</li>
    </ul>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class UsersComponent {
  users$ = this.api.loadUsers(); // no manual subscribe
}
                  

Pattern #2: takeUntilDestroyed (modern Angular, recommended)

This is the cleanest way to auto-unsubscribe in code in Angular 16+ using DestroyRef.

TYPESCRIPT
import { Component, DestroyRef, inject } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';

@Component({ selector: 'app-demo', template: '' })
export class DemoComponent {
  private destroyRef = inject(DestroyRef);

  ngOnInit() {
    this.someStream$
      .pipe(takeUntilDestroyed(this.destroyRef))
      .subscribe(value => {
        // safe: auto-unsubscribed on destroy
      });
  }
}
                  

Pattern #3: Classic takeUntil + Subject (works in all Angular versions)

TYPESCRIPT
import { Component, OnDestroy } from '@angular/core';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';

@Component({ selector: 'app-demo', template: '' })
export class DemoComponent implements OnDestroy {
  private destroy$ = new Subject<void>();

  ngOnInit() {
    this.someStream$
      .pipe(takeUntil(this.destroy$))
      .subscribe(value => {
        // safe
      });
  }

  ngOnDestroy() {
    this.destroy$.next();
    this.destroy$.complete();
  }
}
                  

Pattern #4: Manual Subscription aggregation (old-school)

Works, but easy to forget to add new subscriptions. Not recommended for large components.

TYPESCRIPT
import { Component, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';

@Component({ selector: 'app-demo', template: '' })
export class DemoComponent implements OnDestroy {
  private sub = new Subscription();

  ngOnInit() {
    this.sub.add(this.someStream$.subscribe());
    this.sub.add(this.otherStream$.subscribe());
  }

  ngOnDestroy() {
    this.sub.unsubscribe();
  }
}
                  

Non-RxJS leaks: event listeners & timers

Leaks don’t only come from Observables. addEventListener, setInterval, and 3rd-party libraries must also be cleaned up in ngOnDestroy.

TYPESCRIPT
ngOnInit() {
  window.addEventListener('resize', this.onResize);
  this.timer = setInterval(() => {}, 1000);
}

ngOnDestroy() {
  window.removeEventListener('resize', this.onResize);
  clearInterval(this.timer);
}
                  

Pattern

When to use

Why

async pipe

Template-driven streams

Safest, zero-leak, integrates with OnPush.

takeUntilDestroyed

Subscriptions in component code (Angular 16+)

Modern, concise, no boilerplate.

takeUntil + Subject

Older Angular or shared base classes

Battle-tested and explicit.

Manual Subscription

Very small/simple components

Easy to forget and cause leaks.

Which unsubscribe strategy should you pick?

Senior pitfalls

• Assuming Angular auto-unsubscribes everything (it doesn’t)
• Forgetting that ActivatedRoute, valueChanges, Subjects never complete
• Subscribing in services that live forever (root scope)
• Leaking intervals, event listeners, and WebSocket connections

Interview summary

To prevent memory leaks in Angular: prefer the async pipe in templates, use takeUntilDestroyed (or takeUntil + Subject) in code, know which Observables complete automatically (HTTP) and which don’t, and always clean up timers and event listeners in ngOnDestroy.

Similar questions
Guides
18 / 37