In RxJS, how does a BehaviorSubject differ from a plain Subject, and what does a late subscriber receive from each?
answer
- one holds state, one does not
- constructor argument is required
- current value on subscribe
- synchronous read with getValue
- after complete: no replay
basics
~20 sA 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 sA `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 linesimport { 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
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.
Explain getValue(), what late subscribers get after complete() and error(), and how BehaviorSubject differs from ReplaySubject(1).
Choose between events and state deliberately, avoid synchronous getValue() reads inside reactive code, and never close a shared subject by calling unsubscribe() on it.
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