The Role of a Subject in RxJS
A Subject acts as a bridge that enables multiple observers to subscribe and receive values pushed through it. Think of it as an EventEmitter maintaining a list of listeners; whenever a new value arrives, it is forwarded to every registered listener. This pattern of distributing values to several observers is referred to as multicasting in RxJS terminology.
Consider this basic illustration:
const subject = new Subject();
subject.subscribe((value) => console.log(value));
subject.subscribe((value) => console.log(value));
subject.subscribe((value) => console.log(value));
subject.next('some value');
Here, the subject has three active subscriptions. As soon as the subject receives a value via next, all three subscribers get that value simultaneously.
One notable trait of a Subject is that it functions as both an Observable and an Observer. Because it implements the Observable interface, it exposes the subscribe method, allowing consumers to listen for values. At the same time, being an Observer means you can invoke the next method directly on it, similar to how you would inside a custom Observable's logic where you receive an observer as an argument:
const observable = new Observable((observer) => {
observer.next('some value');
});
To shield the subject's emit capability from external code, the asObservable method is provided:
function listen() {
const subject = new Subject();
// some code here that calls `subject.next`
return subject.asObservable();
}
In this example, only the listen function owns the ability to trigger .next. Any external attempt to invoke next results in a TypeScript error, specifically: Property next does not exist on type Observable<unknown>:
const subject = listen();
subject.next('d'); // produces the error
Subject Variants
RxJS offers four distinct Subject types, each tailored to different scenarios. Here’s a short overview of each.
Subject
This is the standard Subject. It does not retain past values and delivers no initial value. Observers only receive emissions that occur after they subscribe.
AsyncSubject
An AsyncSubject only delivers the final value from its source Observable, and it does so exclusively when the source completes. If the source never emits anything, the AsyncSubject also completes without sending anything. The same last value is replayed to late subscribers. However, if the source fails with an error, the AsyncSubject skips value emission entirely and simply relays that error to its observers.
Find a deeper read here.
BehaviorSubject
When an observer subscribes to a BehaviorSubject, it immediately receives the most recent value from the source Observable, or a provided seed value if none exists yet. After this initial emission, it continues to forward all subsequent source values. In case the source Observable encounters an error, the BehaviorSubject drops further value delivery and passes the error directly to its observers.
For more details, check this resource.
ReplaySubject
A ReplaySubject ensures every observer receives the complete history of values emitted by the source Observable, regardless of when they subscribe. Special configurations exist that discard older values if the buffer grows beyond a specified limit or once values exceed a certain age.
Additional insights are available here.

