skip to content

In RxJS, given of(1, 2, 3, 4, 5, 1), what do take(3), takeWhile, skip(2) and skipWhile each emit with the predicate x < 3?

level: juniorimportance: should knowfreq 50%

answer

  1. count versus predicate
  2. which ones complete early
  3. inclusive flag on takeWhile
  4. skipWhile never re-checks

basics

~20 s

take(3) emits 1, 2, 3 and completes; takeWhile(x => x < 3) emits 1, 2 and completes at the 3; skip(2) and skipWhile(x => x < 3) both emit 3, 4, 5, 1, since skipWhile stops checking once its predicate fails.

solid answer

~40 s

`take(3)` passes the first three values, `1, 2, 3`, then completes and unsubscribes from the source. `takeWhile(x => x < 3)` passes `1, 2` and completes on the first failing value, `3`, which it drops — pass `true` as the second argument (`inclusive`) to emit that value too, giving `1, 2, 3`. `skip(2)` drops the first two and passes `3, 4, 5, 1`; it never completes early. `skipWhile(x => x < 3)` drops until the predicate first returns `false`, then lets everything through, so the trailing `1` passes even though it matches. Contrast `filter(x => x < 3)`, which tests every value and emits `1, 2, 1`. The `take` family ends the stream; the `skip` family and `filter` only drop values.

code

ts · 10 lines
ts
import { of, take, takeWhile, skip, skipWhile, filter } from 'rxjs';

const source$ = of(1, 2, 3, 4, 5, 1);

source$.pipe(take(3)).subscribe(console.log);                    // 1 2 3
source$.pipe(takeWhile(x => x < 3)).subscribe(console.log);      // 1 2
source$.pipe(takeWhile(x => x < 3, true)).subscribe(console.log); // 1 2 3
source$.pipe(skip(2)).subscribe(console.log);                    // 3 4 5 1
source$.pipe(skipWhile(x => x < 3)).subscribe(console.log);      // 3 4 5 1
source$.pipe(filter(x => x < 3)).subscribe(console.log);         // 1 2 1

go deeper

for a junior

Recall the output of each operator on a simple sequence and which ones complete the stream early.

for a middle

Explain the inclusive flag, the index argument to predicates, and why skipWhile stops checking after the first failure.

for a senior

Pick the operator that bounds an endless feed, and spot a filter that should have been a takeWhile in a stream that never completes.

for a principal

Relate operator choice to resource lifetime: which streams must end on their own and which rely on an outside signal to stop.

## Two families: ending versus dropping RxJS's filtering operators split by what they can do to the **lifetime** of a stream. - The **take family** — `take`, `takeWhile`, `takeUntil`, `first` — can **complete** the output before the source finishes, which also unsubscribes from the source. - The **skip family** — `skip`, `skipWhile` — and **`filter`** only decide which values pass. The output completes when the source completes, never earlier. That distinction matters for streams that never end on their own, such as `interval` or a live sensor feed: only the take family can stop them. ## The same source through each operator For `of(1, 2, 3, 4, 5, 1)`: | operator | emits | completes | |---|---|---| | `take(3)` | `1, 2, 3` | after the third value | | `takeWhile(x => x < 3)` | `1, 2` | on `3`, which is dropped | | `takeWhile(x => x < 3, true)` | `1, 2, 3` | on `3`, which is emitted | | `skip(2)` | `3, 4, 5, 1` | with the source | | `skipWhile(x => x < 3)` | `3, 4, 5, 1` | with the source | | `filter(x => x < 3)` | `1, 2, 1` | with the source | The interesting rows are the last value, `1`: - `takeWhile` never sees it — the stream already completed at `3`. - `skipWhile` lets it through — after the predicate first returns `false`, the operator **stops calling the predicate** for good. - `filter` tests it like every other value and emits it. ## Details from the RxJS 7.8 source 1. **`take(count)` with `count <= 0`** returns `EMPTY`: the output completes immediately and the source is never subscribed. 2. **`take` completes as soon as the count is reached**, not when the next value arrives, so `take(1)` on a slow source ends right after the first value. 3. **`takeWhile(predicate, inclusive = false)`** — `inclusive` defaults to `false`. With `true`, the value that failed the predicate is emitted before completion, handy when the terminating value is meaningful (the reading that crossed a threshold). 4. **Predicates receive an index**: `filter`, `takeWhile` and `skipWhile` call `predicate(value, index)`, so `filter((_, i) => i % 10 === 0)` keeps every tenth value. `skip(n)` is literally implemented as `filter((_, index) => n <= index)`. 5. **Type narrowing**: passing `Boolean` (for example `filter(Boolean)`) or a type guard narrows the output type; the `thisArg` overload of `filter` is deprecated in 7.8 in favour of a closure. ## Picking one in a sensor pipeline - Stop after a fixed number of samples: `take(n)`. - Record until a reading crosses a limit, including that reading: `takeWhile(r => r.celsius < 90, true)`. - Ignore the warm-up readings: `skip(n)` for a known count, `skipWhile(r => r.celsius === 0)` while the sensor reports zero. - Keep only valid readings for the whole life of the stream: `filter(r => r.ok)`. ## Why ending early matters Completing is not cosmetic. When `take` or `takeWhile` completes the output, RxJS **unsubscribes from the source**, which runs the source's teardown: a timer is cleared, a DOM listener is removed, a WebSocket subscription is released. `filter`, `skip` and `skipWhile` never trigger that teardown on their own — they keep the source running for as long as someone is subscribed. Completion also unlocks operators downstream that wait for it: - `toArray()` and `reduce()` emit their result only when the stream completes; - `last()` needs completion to know which value was last; - a `complete` callback in the subscriber runs, which is where a UI often hides a spinner. So `interval(1000).pipe(take(5), toArray())` emits one array after five seconds, whereas `interval(1000).pipe(filter(x => x < 5), toArray())` never emits at all. ## Interview traps A frequent mistake is using `filter` where `takeWhile` was meant and wondering why a stream never completes, or using `skipWhile` expecting it to re-check later values the way `filter` does.

  • What does takeWhile's second argument change?
    It is `inclusive`, `false` by default. With `true`, the first value that fails the predicate is emitted before the stream completes, so `takeWhile(x => x < 3, true)` on `1, 2, 3, 4` emits `1, 2, 3`.
  • What does take(0) do to the source subscription in RxJS 7.8?
    `take` with a count of zero or less returns `EMPTY`, so the output completes immediately and the source is never subscribed; no error is thrown.

saying these in an interview costs you the question

  • skipWhile keeps testing every value, just like filter.
  • takeWhile emits the value that fails its predicate by default.
  • skip(n) completes the stream once n values have been skipped.
  • filter completes the stream at the first value that fails the predicate.
  • take(0) subscribes to the source and drops every value.