In RxJS, why does pairwise() emit nothing for the first value, and how does startWith() change what it emits?
answer
- a pair needs two values
- [previous, current] tuples
- a synthetic first value
- emitted before the source
basics
~10 spairwise emits [previous, current] tuples, so it needs two values before its first emission. Putting startWith(x) before it supplies a synthetic first value, so the first real value produces [x, first] immediately.
solid answer
~30 s`pairwise()` remembers the last value and, from the second value on, emits `[previous, current]`. The first value has no predecessor, so it produces no output; a source of N values yields N-1 pairs. `startWith(...values)` emits its arguments synchronously on subscribe, before subscribing to the source, so `startWith(0), pairwise()` turns a cart-total stream `10, 25` into `[0, 10], [10, 25]` - handy for showing "+10" on the first addition. Order matters: `startWith` after `pairwise` would inject a raw value into a stream of tuples. Passing a scheduler to `startWith` is deprecated.
code
ts · 15 linesimport { Subject, map, pairwise, scan, startWith } from 'rxjs';
const addedAmounts$ = new Subject<number>();
const addedDelta$ = addedAmounts$.pipe(
scan((total, amount) => total + amount, 0),
startWith(0),
pairwise(),
map(([before, after]) => after - before),
);
addedDelta$.subscribe((d) => console.log(`+${d}`));
addedAmounts$.next(10); // +10
addedAmounts$.next(15); // +15go deeper
Recall that pairwise emits previous and current values together and that startWith prepends a value before the source.
Explain why N values give N-1 pairs, that startWith emits synchronously on subscribe, and why startWith must come before pairwise to seed it.
Choose domain-correct seeds, avoid adding a second startWith to a stream that already begins with one, and use pairwise deltas for change indicators without extra state.
Judge when previous-versus-current comparisons belong in the stream and when they belong in a state layer that already tracks history.
## What pairwise emits `pairwise()` is a transformation operator that turns a stream of values into a stream of **consecutive pairs**. It stores the most recent value and, when the next arrives, emits a tuple `[previous, current]`, typed `[T, T]`. | Source values | `pairwise()` output | |---|---| | `a` | nothing | | `a, b` | `[a, b]` | | `a, b, c` | `[a, b]`, `[b, c]` | The first value has no predecessor, so it is only remembered. A source of **N** values therefore yields **N - 1** pairs, and a single-value source yields none. `pairwise` passes errors and completion through unchanged and does not emit a final pair on completion. Typical uses: - computing a **delta** between successive readings - a cart total, a price, a counter; - detecting **direction** - up or down, forward or back; - comparing the previous and current state of an object to decide what changed. ## What startWith does `startWith(...values)` prepends values to a stream. In RxJS 7 it is implemented by concatenating the given values with the source, which has two consequences: 1. the values are emitted **synchronously, on subscribe**, before the source is even subscribed; 2. they are emitted in argument order, and then the source's values follow. `endWith(...values)` is its mirror image, appending values after the source completes. ## Combining them Because `pairwise` swallows the first value, a UI that shows the change since the last update shows nothing for the first update. Seeding the stream with `startWith` fixes it: - `total$.pipe(startWith(0), pairwise())` over totals `10, 25` emits `[0, 10]` and `[10, 25]`; - mapping each pair to `curr - prev` gives `+10` and `+15`. The seed should be a real "previous" value for the domain - `0` for an empty cart, the persisted value for a restored session - otherwise the first delta is meaningless. **Order matters.** `pairwise(), startWith([0, 0])` would put a tuple at the front of the output, unrelated to the source; `startWith(0), pairwise()` gives `pairwise` a predecessor. And a stream that already begins with a seed - for example `scan(...)` followed by `startWith(0)` - must not get a second `startWith(0)` before `pairwise`, or the first pair becomes `[0, 0]`. ## The cart-total scenario A running cart total is built with `scan` over cart additions. The header wants to flash the amount just added: 1. `scan` produces the running total after each addition; 2. `startWith(0)` gives an initial total for an empty cart; 3. `pairwise()` turns totals into `[before, after]`; 4. `map(([before, after]) => after - before)` gives the amount added. With this ordering, the very first addition already produces a delta. ## Alternatives to pairwise - **`bufferCount(2, 1)`** also produces overlapping pairs, but it is not a drop-in replacement: when the source completes it flushes the still-open buffer, so the last emission is a one-element array such as `[25]`. - **`scan`** can carry the previous value explicitly - `scan((acc, curr) => ({ prev: acc.curr, curr }), { prev: 0, curr: 0 })` - which is useful when more than two values of history are needed. - For most delta and direction checks, `startWith` plus `pairwise` is the shortest correct form, and its `[T, T]` tuple type documents the intent. ## Details worth knowing - `pairwise` keeps **one** previous value per subscription; each subscriber gets its own pairing. - The tuple is a new array each time, so reference-based change checks see every pair as new. - `startWith(null)` and `startWith(undefined)` are valid and widen the type to include `null` or `undefined`. - Passing a scheduler as the last argument to `startWith` is deprecated since RxJS 6.5 and scheduled for removal in v8. ## Summary `pairwise` needs two values to form a pair, so the first is silent; `startWith` supplies a synthetic first value synchronously, and placed before `pairwise` it makes the first real value produce a pair.
- In RxJS, what does of(5).pipe(pairwise()) emit?Nothing but completion. With a single value there is no previous value to pair with, and `pairwise` does not emit an incomplete pair when the source completes. Adding `startWith(0)` before it would produce `[0, 5]`.
- In RxJS, when does startWith emit its values relative to subscribing to the source?Before it. `startWith` concatenates its values with the source, so on subscribe it emits them synchronously, in argument order, and only then subscribes to the source. That is why a UI bound to `source$.pipe(startWith(initial))` has a value immediately, even when the source is asynchronous.
pairwise is a turnstile counter that can only report 'how many since last time' once it has a previous reading; startWith is writing the opening reading on the clipboard before the doors open.
saying these in an interview costs you the question
- pairwise emits [undefined, first] for the first value.
- pairwise emits a final pair containing the last value when the source completes.
- startWith emits its value after the source's first value arrives.
- Placing startWith after pairwise gives pairwise a seed value.
- Passing a scheduler to startWith is the current way to make its value asynchronous.