Most Angular memory leaks come from long-lived streams, global listeners, or timers that outlive a component. The practical angle is knowing when AsyncPipe, takeUntilDestroyed, or manual teardown actually matters and when unsubscribe boilerplate is unnecessary.
How Do You Prevent Memory Leaks in Angular? All Real-World Unsubscribe Patterns Explained
Teardown mental model
Most Angular memory leaks are not mysterious framework bugs. They come from long-lived streams, timers, or global listeners that outlive a component. The production question is not “unsubscribe from everything?” but “which sources complete on their own, and which ones keep consuming memory, CPU, or network until I tear them down?”
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.
Use the relevant interview-question hub first, then move into a concrete study plan before targeted company sets.