skip to content

Subjects and Multicasting

Subject, BehaviorSubject, ReplaySubject and AsyncSubject are observers you can push into, and share and shareReplay multicast one source. Interviewers ask each one's late-subscriber behaviour.

part ofRxJSoverview, primer and where to startread it →
on this pageshow

explore

questions

5

In RxJS, how does a BehaviorSubject differ from a plain Subject, and what does a late subscriber receive from each?

level: middleimportance: must knowfreq 80%

answer

  1. one holds state, one does not
  2. constructor argument is required
  3. current value on subscribe
  4. synchronous read with getValue
  5. after complete: no replay

basics

~20 s

A plain Subject forwards only values pushed after you subscribe; a BehaviorSubject requires an initial value, always holds the latest one, emits it immediately to each new subscriber and exposes it synchronously through getValue() or value.

solid answer

~40 s

A `Subject` keeps no state: a subscriber sees only values passed to `next()` **after** it subscribed, and anything pushed earlier is gone. A `BehaviorSubject` is constructed with an **initial value**, remembers the latest value, and on subscription immediately emits that current value, then every later one. It also lets you read the current value synchronously with `getValue()` or the `value` getter, which makes it the usual holder for 'current state' such as the selected item or a loading flag. Two edges are asked about: after `complete()`, a new subscriber receives **only** the completion, not the last value (a `ReplaySubject(1)` would replay it); and after `error()`, `getValue()` throws that error instead of returning a value.

code

ts · 11 lines
ts
import { BehaviorSubject } from 'rxjs';

const status = new BehaviorSubject<'idle' | 'loading'>('idle');

status.error(new Error('backend down'));

try {
  status.getValue();
} catch (e) {
  console.log('getValue threw:', (e as Error).message); // backend down
}

go deeper

for a junior

Recall that a BehaviorSubject needs an initial value and gives every new subscriber the current value at once, while a Subject only delivers future values.

for a middle

Explain getValue(), what late subscribers get after complete() and error(), and how BehaviorSubject differs from ReplaySubject(1).

for a senior

Choose between events and state deliberately, avoid synchronous getValue() reads inside reactive code, and never close a shared subject by calling unsubscribe() on it.

for a principal

Set a convention for state holders: which values are events and which are state, where the seed comes from, and whether consumers may read synchronously at all.

## What a Subject is In RxJS a **Subject** is an object that is both an **Observable** (you can `subscribe` to it) and an **Observer** (you can call `next`, `error` and `complete` on it). Every value passed to `next()` is delivered to every observer subscribed at that moment. It is the simplest way to push values into a stream from imperative code. A plain `Subject` has **no memory**: - A subscriber receives only the values pushed **after** it subscribed. - A value pushed while nobody is subscribed is delivered to nobody and is lost. - There is no way to ask a plain `Subject` "what was the last value?". ## What BehaviorSubject adds `BehaviorSubject<T>` is a `Subject` subclass that models **a value that changes over time** rather than a series of events: 1. Its constructor **requires an initial value**: `new BehaviorSubject<string>('idle')`. 2. Every `next(v)` stores `v` as the current value and then forwards it. 3. When anyone subscribes, it first receives the **current value** synchronously, then every later value. 4. `getValue()` — and the `value` getter, which calls it — returns the current value without subscribing. So a subscriber that arrives late never has to wait for "the next change" to know the state; it receives the state at once. ## Side by side | | `Subject` | `BehaviorSubject` | |---|---|---| | Constructor | no arguments | initial value required | | Late subscriber while active | nothing until the next `next()` | the current value, immediately | | Synchronous read | not available | `getValue()` / `value` | | Late subscriber after `complete()` | completion only | completion only, **no** value | | Late subscriber after `error()` | the error | the error | | `getValue()` after `error()` | — | throws the stored error | ## The edges interviewers probe - **Completed BehaviorSubjects do not replay.** Once `complete()` has been called, a new subscriber gets only the completion notification. The RxJS source documents two differences between `BehaviorSubject` and `new ReplaySubject(1)`: the `BehaviorSubject` comes primed with an initial value, and the `ReplaySubject` keeps replaying after an error where the `BehaviorSubject` does not. The same replay path also runs after completion. - **`getValue()` after completion still works.** The last value remains readable; only after `error()` does `getValue()` throw — it rethrows the error the subject was errored with. - **Calling `unsubscribe()` on the subject itself** (not on a subscription) closes it for good: later `next()`, `subscribe()` and `getValue()` calls throw an `ObjectUnsubscribedError`. To stop listening, unsubscribe the `Subscription`, not the subject. - **Reading `getValue()` everywhere is a smell.** It is a synchronous escape hatch; code that reads it inside other streams usually wants to combine the subject as a stream instead, so it reacts to changes. ```ts import { BehaviorSubject, Subject } from 'rxjs'; const events = new Subject<string>(); const status = new BehaviorSubject<string>('idle'); events.next('clicked'); // nobody subscribed: lost status.next('loading'); // stored as the current value events.subscribe((v) => console.log('events', v)); // logs nothing yet status.subscribe((v) => console.log('status', v)); // status loading console.log(status.getValue()); // 'loading' status.complete(); status.subscribe({ next: (v) => console.log('late', v), complete: () => console.log('late complete'), }); // late complete (no value replayed after completion) console.log(status.value); // 'loading' is still readable ``` ## Choosing between them - Use a **`Subject`** for **events** where only future occurrences matter: a button was clicked, a message arrived, a refresh was requested. Replaying an old event to a new subscriber would be wrong. - Use a **`BehaviorSubject`** for **state** that always has a current value: the selected tab, the signed-in user (or `null`), a filter. Every consumer needs the current value on arrival. - If there is no sensible initial value, a `ReplaySubject(1)` gives "latest value to late subscribers" without inventing a seed — at the cost of having no synchronous `getValue()`.

  • How is a BehaviorSubject different from new ReplaySubject(1)?
    Both give a late subscriber the latest value. A `BehaviorSubject` needs an initial value, so it always has one, and exposes it synchronously through `getValue()`. A `ReplaySubject(1)` has no seed — a subscriber before the first `next()` gets nothing — and no `getValue()`, but it keeps replaying its buffered value even after it has completed or errored, where a `BehaviorSubject` sends only the terminal notification.
  • What happens if you call unsubscribe() on a Subject instead of on a Subscription?
    The subject is closed permanently and its observer list is dropped. Any later `next()`, `subscribe()` or, for a `BehaviorSubject`, `getValue()` throws an `ObjectUnsubscribedError`. To stop one consumer from listening, call `unsubscribe()` on the `Subscription` that `subscribe()` returned, which leaves the subject working for everyone else.

A Subject is a live radio broadcast: tune in late and you only hear what plays from now on. A BehaviorSubject is a departure board: whenever you walk up, it already shows the current state, and it changes in front of you.

saying these in an interview costs you the question

  • A plain Subject replays its last value to new subscribers
  • A BehaviorSubject can be created without an initial value
  • A completed BehaviorSubject still emits its last value to new subscribers
  • getValue() on an errored BehaviorSubject returns the last value before the error
  • Calling unsubscribe() on the subject is how a single consumer stops listening
open as a page

In RxJS, what makes a Subject both an Observable and an Observer, and how does that let one source execution feed many subscribers?

level: juniorimportance: should knowfreq 55%

basics

~20 s

A Subject has subscribe() like an Observable and next(), error() and complete() like an Observer, keeping a list of subscribers; subscribing it to a source runs that source once and rebroadcasts every value to all of them.

open as a page

In RxJS, a chat room's messages$ must show late joiners recent history; how do ReplaySubject's bufferSize and windowTime decide what they get?

level: middleimportance: should knowfreq 50%

basics

~10 s

ReplaySubject(bufferSize, windowTime) keeps at most bufferSize values that are younger than windowTime milliseconds and replays them in order to each new subscriber before live values; both limits default to infinity.

open as a page

In RxJS, why can shareReplay(50) keep a chat room's socket stream open after every subscriber has left, and how do you fix it?

level: seniorimportance: should knowfreq 60%

basics

~20 s

shareReplay's refCount defaults to false, so when the subscriber count drops to zero its inner ReplaySubject stays subscribed to the source; for a never-completing source use shareReplay({ bufferSize, refCount: true }) or share with explicit reset options.

open as a page

In RxJS 7, what replaced the deprecated multicast, publish and refCount operators, and when would you use connectable() instead of share()?

level: seniorimportance: nice to knowfreq 24%

basics

~10 s

RxJS 7 reduced multicasting to connectable, connect, share and shareReplay; use connectable() when you must attach every subscriber before the source starts, because share() starts it on the first subscription.

open as a page