second$ = interval(1000).pipe(
map(x => x+1)
);
second$ = interval(1000).pipe(
map(x => x+1)
);
Fact Summary
A Subject serves as a multicasting mechanism, distributing events to numerous subscribers. Usually, there is a single shared producer operating underneath, although multiple producers are theoretically possible (though uncommon).
When no Subject is involved, the observable unicasts. Each new subscriber triggers the immediate creation of a fresh producer.
Back to π₯ hot 32 π₯
We now have the necessary groundwork to explore all the potential configurations.
These scenarios apply whenever a Subject is present (i.e., the stream is "hot").
reset on error
When one of the producers delivers an ERROR notification to the subject, should the subject reset itself? Should it re-establish the connection? In our terms, should a new producer be instantiated?
reset on complete
This is analogous to the error case, but it concerns the COMPLETE notification.
reset on ref count zero
Consider a subject that initially has no subscribers. A first subscriber arrives, followed by a second. The subject continuously tracks the number of active subscribers. As subscribers leave and unsubscribe, this counter (the ref count) decreases.
This leads to a critical question: if the refCount hits zero (meaning no active subscribers), should the connection to the producer remain active? In other words, should we keep the producer alive?
It's important to remember that a live producer will continue to emit items according to its own logic (e.g., an interval will keep ticking).
Where did you get these from?
Now we get to the most interesting part π β the source code. Let's examine the RxJS ShareReplay implementation. It's particularly instructive because it clearly defines the configurable parameters that the share operator exposes:
The shareReplay operator is essentially a specialized form of share. It accepts a configuration object, sets up a new config to pass along, and then simply delegates to the share() operator.
The connector is a factory function that returns a subject of any type. It could even return a subject that already has subscribers, though this is not the typical case.
The resetOnError, resetOnComplete, and resetOnCountZero options correspond to the scenarios we've already discussed π. This configuration allows us to determine all of these behaviors.
How important is reading the source code?
Sometimes it's less critical. But in this case, it's absolutely essential! Think about all the instances where you use share() or shareReplay() and simply assume it will work out. That's not enough! Consider real-world scenarios like route changes during navigation, which might momentarily drop the refCount to zero. Or a complete notification that might inadvertently break your application's logic.
How hot π₯ are your streams?
Being "hot" fundamentally refers to multicasting β that is, sharing the connection to the producer, or in practice, sharing the producer itself. However, I trust you'll agree that these distinct situations:
- always sharing the same producer
- sharing the producer unless a
RESETis emitted - sharing the producer unless a
COMPLETEis emitted - sharing only while there are active subscribers
... each exhibit entirely different runtime characteristics. Overlooking these differences can result in significant bugs or unexpected behavior.
One more case
As noted earlier, there are more than 32 distinct forms of "hotness". How is that possible? We've already touched upon connectable observables. But there's another, somewhat exotic but technically achievable, scenario. This involves an observable that receives values from a producer created entirely independent of any observable context. Imagine a producer that, upon creating a new value, invokes a function β it does this regardless of whether any observables or subscribers exist. When a new subscriber appears, the void function is simply replaced or augmented (depending on the implementation) to supply the subscriber. The producer's lifecycle is completely decoupled from the subscribers; crucially, the producer is not instantiated within a subscription context.
That's just an unusual example, but the takeaway is clear: don't fixate on the precise number 32. Understand that there are numerous variations of "hotness".
Conclusion
The terms unicasting and multicasting offer a precise way to describe whether a subject is utilized. This distinction alone is far more accurate than the overly simplistic "cold and hot" categorization.
A cold stream is a lazy, unicasted stream that instantiates a new producer for every single subscriber.
Hot streams, however, are more nuanced. They may or may not be lazy (think of Connectable), and they might share the producer until certain events occur. Moreover, different types of subjects employ completely different emission strategies. These variations are so significant that it's counterproductive to lump them all together under one term β a term that often discourages people from exploring deeper.
So, the next time you reach for a Subject, don't just default to share() or shareReplay() (and wonder why there isn't a shareBehavior() either?!). Deliberate over all 32 possible configurations β one of them is the precise fit for your needs. For instance,
stream$.pipe(
share({
connector: new BehaviorSubject("INITIAL"),
resetOnError: true,
resetOnComplete: false,
resetOnRefCountZero: false,
})
)
And when we revisit the official definition of a "hot observable":
An observable is "hot", when its producer was created outside of the context of the subscribe action. [...]
we see that this definition specifies neither the behavior nor the lifespan of the producer, which is where the real differences lie.
My strong recommendation: once you've mastered the fundamentals of Observables, adopt more precise terminology and move away from the ambiguous "hot" (and "cold") stream metaphor. It creates a false dichotomy and gives the impression that things are straightforward.
The more we learn, the clearer it becomes that they are not.
I want to give special thanks to Jan-Niklas Wortmann and Bartosz Cytrowski for proof reading this article πͺ
Follow me on twitter for more frontend (js, ts, react, angular etc.) deep dives!




