shareReplay is an RxJS operator that turns a cold observable into a shared hot one and caches the last emitted values. Used incorrectly, it can cause memory leaks, stale data bugs, and unexpected behavior—especially in Angular services and HTTP streams.
What Is shareReplay in RxJS and How Can It Silently Break Your Angular App?
Use guided tracks for structured prep, then practice company-specific question sets when you want targeted interview coverage.
Core ideashareReplay does two things at once:
• It shares a single subscription among multiple subscribers.
• It replays the last N emitted values to new subscribers.
This is extremely useful for caching HTTP calls—but also extremely easy to misuse.
The classic Angular use-case
You want multiple components to consume the same HTTP data without triggering multiple requests.
users$ = this.http.get<User[]>('/api/users').pipe(
shareReplay(1)
);
What problem does this solve?
Without shareReplay, every subscribe() or async pipe would trigger a new HTTP request. With shareReplay(1), the first request is cached and reused.
Behavior | Without shareReplay | With shareReplay(1) |
|---|---|---|
Second subscriber | Triggers new HTTP request | Gets cached value |
Third subscriber | Triggers new HTTP request | Gets cached value |
Network usage | Many duplicate calls | Single call |
How shareReplay can silently break your app
Problem | Why it happens | Symptom in real apps |
|---|---|---|
Memory leaks | By default, shareReplay keeps the source subscribed forever. | Services never released, websockets/intervals never stop. |
Stale data | Cached value is reused even when data should refresh. | User navigates away/back and sees outdated data. |
Error caching | If the source errors, the error is also replayed. | Stream is permanently broken until reload. |
Unexpected global state | shareReplay turns cold streams into hot singletons. | Components influence each other indirectly. |
The most important option: refCount
Modern RxJS lets you write:
users$ = this.http.get<User[]>('/api/users').pipe(
shareReplay({ bufferSize: 1, refCount: true })
);
What does refCount: true do?
It tells RxJS: “When there are no subscribers left, unsubscribe from the source and free resources.” Without this, the source stays alive forever.
Configuration | When last subscriber unsubscribes | Memory behavior |
|---|---|---|
shareReplay(1) | Source stays subscribed | ❌ Potential leak |
shareReplay({ bufferSize: 1, refCount: true }) | Source unsubscribes | ✅ Safe cleanup |
Another subtle trap: error caching
If the HTTP call errors once, shareReplay will replay that error forever. All future subscribers instantly get the error, and the stream never retries unless you rebuild it or add retry logic.
users$ = this.http.get<User[]>('/api/users').pipe(
retry(1),
shareReplay({ bufferSize: 1, refCount: true })
);
Senior rule of thumb
• Use shareReplay for caching expensive or shared streams.
• Prefer { bufferSize: 1, refCount: true } unless you intentionally want a forever-alive cache.
• Be explicit about refresh/invalidation semantics.
• Never blindly slap shareReplay(1) everywhere.
Angular scenario | Should you use shareReplay? | How |
|---|---|---|
Config loaded once at app startup | ✅ Yes | shareReplay(1) without refCount may be OK |
User-specific API data | ⚠️ Be careful | Use refCount or explicit refresh trigger |
WebSocket / interval stream | ❌ Usually dangerous | You probably want manual lifecycle control |
HTTP list used by many components | ✅ Yes | shareReplay({ bufferSize: 1, refCount: true }) |
Interview summaryshareReplay shares a single subscription and caches the last values. It is great for avoiding duplicate HTTP calls, but can cause memory leaks, stale data, and permanent error states if used carelessly. In Angular, the safe default is shareReplay({ bufferSize: 1, refCount: true }) and a clear cache invalidation strategy.