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.
How Do You Prevent Memory Leaks in Angular? All Real-World Unsubscribe Patterns Explained
Use guided tracks for structured prep, then practice company-specific question sets when you want targeted interview coverage.
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 |
|---|---|---|---|
| ✅ Yes | ❌ No | HTTP observables complete after one response. |
| ❌ No | ✅ Yes | They are long-lived streams tied to app lifetime. |
| ❌ No | ✅ Yes | They never complete unless you stop them. |
| — | ❌ No | Angular unsubscribes automatically on destroy. |
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.
@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.
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)
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.
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.
ngOnInit() {
window.addEventListener('resize', this.onResize);
this.timer = setInterval(() => {}, 1000);
}
ngOnDestroy() {
window.removeEventListener('resize', this.onResize);
clearInterval(this.timer);
}
Pattern | When to use | Why |
|---|---|---|
| Template-driven streams | Safest, zero-leak, integrates with OnPush. |
| Subscriptions in component code (Angular 16+) | Modern, concise, no boilerplate. |
| Older Angular or shared base classes | Battle-tested and explicit. |
Manual Subscription | Very small/simple components | Easy to forget and cause leaks. |
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.