Interview Prep
Operator Cheat Sheet
One page, every decision table. Built for the last hour before an interview.
"I want to..." → Operator
I want to...
Use
Why
Transform each value
map
Pure per-value projection
Run a side effect without changing values
tap
Return value ignored
Accumulate state across emissions
scan
Emits every intermediate result
Call an API per value, latest wins
switchMap
Cancels the stale inner stream
Call an API per value, all in parallel
mergeMap
Optional concurrency cap
Call an API per value, strictly in order
concatMap
Queues; = mergeMap(fn, 1)
Ignore triggers while one is running
exhaustMap
Drops, does not queue
Drop values failing a condition
filter
Stream stays alive
Settle bursty input (typing)
debounceTime
Emits after silence
Steady rate from continuous events
throttleTime
Consider { trailing: true }
Skip unchanged repeats
distinctUntilChanged
=== by default; objects need a comparator
First N values then stop
take
Completes + unsubscribes
Stop on an external signal
takeUntil
Keep it last in the pipe
Stop when the data says so
takeWhile
inclusive: true for the final value
Combine latest values of live streams
combineLatest
Silent until all emit once
All parallel calls, one final result
forkJoin
Inputs must complete
Attach current state to an event
withLatestFrom
Only the source triggers
Funnel independent triggers into one stream
merge
First come, first served
One stream after another completes
concat
Cache-then-network
Give a stream an immediate first value
startWith
Unblocks combineLatest; loading states
Recover from errors with a fallback
catchError
Must return an Observable
Retry transient failures
retry
{ count, delay }; gate on error type
Bound waiting time
timeout
Number = every gap; with for fallback
Guaranteed cleanup
finalize
Runs on complete, error, AND unsubscribe
Share one execution, live only
share
Resets by default at zero subscribers
Share + cache for late subscribers
shareReplay
{ bufferSize: 1, refCount: true }
Higher-Order Mapping: The Quadrant
Keep all inner streams
One at a time
New value interrupts
—
switchMap (cancel the old)
New value waits/joins
mergeMap (parallel)
concatMap (queue)
New value is dropped
—
exhaustMap (protect the current)
Triple-click Save: mergeMap 3 racing requests · concatMap 3 sequential · switchMap cancels first 2 · exhaustMap 1 request, 2 drops. Full scenario: the comparison .
Subject Family
Initial value
New subscriber gets
Sync read
After complete()
Subject
No
Nothing
No
Complete only
BehaviorSubject
Required
Current value
getValue()
Complete only
ReplaySubject(n)
No
Last n values
No
Still replays buffer
AsyncSubject
No
Nothing until complete
No
Final value + complete
Timing Operators
Operator
Behavior
Fits
debounceTime(t)
Last value after t of silence
Typing
throttleTime(t)
Value, then cooldown t
Scroll, clicks
auditTime(t)
On activity, wait t, emit latest
Steady sampling of bursts
sampleTime(t)
Every t, emit latest if any
Fixed-clock readouts
delay(t)
Shift each value by t (errors NOT delayed)
Minimum display time
Unsubscribe Strategies
Strategy
When
Notes
Async pipe / toSignal
Display data
No manual subscription at all
takeUntilDestroyed()
Class-code pipelines
Injection context (or pass DestroyRef)
takeUntil(destroy$)
Legacy / explicit control
next() then complete(); keep it last
take(1) / first()
One-shot reads
Self-completing
Manual unsubscribe()
Last resort
Collect with subscription.add()
Not needed for plain HttpClient calls: they complete themselves. Needed for: intervals, fromEvent, Subjects, valueChanges, Router.events.
Error Handling in One Pipeline
http . get ( url ). pipe (
timeout ( 5000 ), // bound each attempt
retry ({ count : 2 , delay : ... }), // transient errors only
catchError (() => of ( fallback )) // final safety net -> UI state
);
Order is the interview point: timeout inside, retry before catchError. Layers: interceptor = transport, service = domain fallback, component = UI state. Details: Interceptors & Retries .
Ten Facts Worth Saying Out Loud
Observables are lazy; nothing runs until subscribe.
Cold = producer per subscriber; hot = shared producer. Unicast/multicast is the same axis.
The contract: next* (error | complete)?, then silence.
Errors are terminal; catchError replaces, retry resubscribes.
Unsubscribe runs teardown but not the complete handler; finalize covers all endings.
switchMap cancels, concatMap queues, exhaustMap drops, mergeMap parallelizes.
Bare shareReplay(1) never disconnects; completed sources cache forever regardless of refCount.
combineLatest is silent until every input emits; fix with startWith.
forkJoin needs completion, not just emission.
Signals hold state; RxJS orchestrates events and async. Bridge with toSignal/toObservable.