Preventing Redundant Execution with RxJS shareReplay
Learn how to use RxJS shareReplay to prevent redundant API calls and heavy computations by multicasting and caching emissions for late subscribers.
09 Jun 2026, 10:40 UTC

The Problem: Duplicate Execution in Cold Observables
By default, most RxJS observables are "cold." This means the producer (the logic inside the observable) starts fresh for every single subscriber. If you have an observable that performs a heavy computation or an HTTP request, and three different components subscribe to it, the request is sent three times.
The shareReplay operator solves this by transforming a cold observable into a "hot" one. It multicasts the source—meaning it maintains one single subscription to the original producer—and caches a specified number of previous emissions to give to any future subscribers immediately upon their arrival.
How shareReplay Works
When the first subscriber arrives, shareReplay subscribes to the source. As values are emitted, they are passed to the current subscriber and stored in an internal buffer. When a second subscriber joins, they do not trigger a new execution of the source; instead, they immediately receive the values stored in the buffer and then receive any new values as they arrive.
Implementation Example
This example demonstrates how to prevent redundant API calls using RxJS 7.x. In this scenario, we simulate a data fetch that should only happen once, even if multiple parts of the application need the result at different times.
import { timer, shareReplay, map } from 'rxjs';
// Simulate an expensive API call that takes 1 second to resolve
const dataFetch$ = timer(1000).pipe(
map(() => ({ data: 'System Configuration Loaded' })),
// bufferSize: 1 keeps only the latest emission
// refCount: true ensures the source is cleaned up when no one is listening
shareReplay({ bufferSize: 1, refCount: true })
);
console.log('Subscriber A joining...');
dataFetch$.subscribe(val => console.log('A received:', val));
// Subscriber B joins 2 seconds later
setTimeout(() => {
console.log('Subscriber B joining...');
dataFetch$.subscribe(val => console.log('B received:', val));
}, 2000);
Expected Behavior:
- The timer starts once when Subscriber A joins.
- After 1 second, Subscriber A logs the data.
- When Subscriber B joins at 2 seconds, they do not wait for a new timer. They immediately receive the cached "System Configuration Loaded" object.
Configuration Options
The shareReplay operator accepts a configuration object with two primary properties:
| Property | Type | Description |
|---|---|---|
bufferSize |
number | How many previous emissions to cache. 1 is most common for state; Infinity caches everything. |
refCount |
boolean | If true, the operator unsubscribes from the source when the subscriber count drops to zero. If false, the source remains active. |
Critical Engineering Decisions: refCount and Memory
The choice of refCount is the most common source of memory leaks in RxJS applications.
When refCount is false (Default in some versions)
The internal subscription to the source is never disposed of, even if all downstream subscribers have unsubscribed. If the source is a long-lived stream (like a WebSocket or a setInterval), it will continue to run in the background indefinitely, consuming memory and CPU.
When refCount is true
The operator tracks the number of active subscribers. When the count hits zero, it unsubscribes from the source. If a new subscriber arrives later, it will re-subscribe to the source. Note that if the source has already completed, the cached value is still delivered to new subscribers without re-executing the source.
Limitations and Common Mistakes
- Unbounded Buffers: Setting
bufferSize: Infinityon a high-frequency stream (like mouse movements or a fast interval) will cause the memory usage of your application to grow linearly over time until it crashes. Always define a numeric limit for high-frequency data. - Late Subscription Timing: If you use
bufferSize: 0, new subscribers will not receive any previous values; they will only see values emitted after they subscribed. This effectively turns the observable into a simple multicast without a cache. - Completion Behavior: Once a source observable completes,
shareReplaywill continue to replay the buffered values to any future subscribers, even ifrefCountis true.
Verification Method
To verify if your shareReplay is working as intended, add a tap operator before the shareReplay call to log a message like 'Source Executing'. If you see this log only once despite having multiple subscribers, the multicasting is functioning correctly.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.