In RxJS, how do debounceTime and throttleTime differ, and which fits autosave while typing versus a scroll-position tracker?
answer
- waits for silence versus rate cap
- which value of a burst
- continuous activity starves one
- leading edge by default
basics
~20 sdebounceTime emits the latest value once the stream has been silent for the given time, which suits autosave after typing pauses. throttleTime emits a value, then ignores the source for a fixed window, which suits steady scroll updates.
solid answer
~40 s`debounceTime(ms)` restarts a timer on every value and emits the **most recent** value only when `ms` passes with no new one. For autosave that is exactly "save once the user pauses"; the catch is that constant activity produces **no** emission at all. `throttleTime(ms)` works the other way: by default (`leading: true, trailing: false`) it emits the **first** value immediately, then ignores the source for `ms`, then repeats. For a scroll-position tracker that gives a steady update rate while the user scrolls. The default drops the values inside each window, including the final position, which is why scroll trackers usually enable the trailing edge or use `auditTime`. Rule of thumb: react after the activity ends — debounce; react regularly during it — throttle.
code
ts · 15 linesimport { fromEvent, map, debounceTime, throttleTime, Observable } from 'rxjs';
declare const draft$: Observable<string>;
declare function saveDraft(text: string): void;
// Autosave: one save per pause in typing
draft$.pipe(debounceTime(1000)).subscribe(text => saveDraft(text));
// Scroll tracker: at most one update per 100 ms while scrolling
fromEvent(window, 'scroll')
.pipe(
throttleTime(100),
map(() => window.scrollY)
)
.subscribe(y => console.log('scrolled to', y));go deeper
Recall that debounceTime waits for a pause and emits the latest value, while throttleTime emits the first value and then ignores the source for a window.
Explain which value each operator emits, why constant activity starves debounceTime, and why default throttling drops the final scroll position.
Choose operator and duration per use case, and account for what is lost: pending edits in a debounce, trailing values in a default throttle.
Set team guidance for input and scroll handling so that responsiveness, request volume and data loss are traded off consistently across features.
## Two ways to thin a busy stream Keystrokes and scroll events arrive far faster than an application wants to act on them. RxJS 7.8 offers time-based operators that let only some of those values through. The two most asked about are `debounceTime` and `throttleTime`, and they answer different questions: - **`debounceTime(dueTime)`** asks: *has the stream gone quiet?* It emits only after `dueTime` milliseconds pass with **no new value**, and what it emits is the **latest** value. - **`throttleTime(duration)`** asks: *may I emit yet?* It emits a value, then **ignores** the source for `duration` milliseconds, then is ready again. Both take an optional scheduler, `asyncScheduler` by default, and both keep their state per subscription. ## Side by side | | `debounceTime(1000)` | `throttleTime(1000)` (default config) | |---|---|---| | first value of a burst | held | emitted immediately | | values during the burst | each replaces the held value and restarts the timer | ignored while the window is open | | when it emits | 1000 ms after the last value | at most once per 1000 ms window, at its start | | which value | the last one before the pause | the first one of each window | | continuous activity | emits **nothing** until it stops | emits steadily | | source completes with a held value | emits it at once, then completes | the ignored values are gone | ## Autosave while typing Autosave wants one request per pause, carrying the latest draft: ```ts draft$.pipe(debounceTime(1000)) ``` 1. The user types `h`, `he`, `hel`… — each value resets the one-second timer. 2. The user stops for a second — `debounceTime` emits `hello`, the current draft. 3. The save runs once instead of once per keystroke. Throttling here would save the *first* keystroke of each window (`h`) and, with the default config, drop the rest — the saved draft would lag behind what the user typed. The same reasoning applies to a search box: debounce the input so the query runs once the user pauses. Cancelling a request that is already in flight when a newer query arrives is a separate job, done by a flattening operator. ## Scroll-position tracker A tracker that updates a progress bar or a "back to top" button wants values **while** the user scrolls: - `debounceTime(100)` would update only after scrolling stops, so the bar freezes during a long scroll; - `throttleTime(100)` updates at most ten times a second throughout the scroll. With its default `{ leading: true, trailing: false }`, however, `throttleTime` emits the position at the **start** of each window and drops everything else. If the scroll ends in the middle of a window, the final position never arrives. The fixes are to enable the trailing edge or use `auditTime`, which emits the latest value at the end of each window. ## Choosing the durations The numbers are product decisions, not constants: | use case | operator | typical range | what a bad value feels like | |---|---|---|---| | autosave | `debounceTime` | 500–2000 ms | too short: a request per word; too long: edits at risk | | search input | `debounceTime` | 200–400 ms | too long: the box feels sluggish | | scroll tracker | `throttleTime` or `auditTime` | 50–150 ms | too long: the indicator visibly jumps | Measure the cost of the downstream work: if each emission triggers a request, the operator is also a rate limit on the server. ## Common mistakes - Assuming `debounceTime` emits "every N ms" — it emits only after silence, so a user who never pauses never triggers it. - Assuming `throttleTime` emits the latest value by default — it emits the first one. - Picking `debounceTime` for a progress indicator and wondering why it lags a full scroll behind. - Using a very long debounce for autosave: the longer the quiet period, the more edits are at risk if the stream is torn down before the timer fires. In an Angular component, the usual sources are a form control's `valueChanges` for the draft and `fromEvent(window, 'scroll')` for the tracker; the operators behave the same regardless of where the values come from.
- What does debounceTime emit if the user types without pausing for a full minute?Nothing until the user stops. Every value restarts the quiet-period timer, so a source that never goes silent for `dueTime` starves the output; only the pause, or the source completing, releases the latest value.
- Why does a throttled scroll tracker sometimes show a stale final position?`throttleTime` defaults to `{ leading: true, trailing: false }`: it emits the first value of each window and drops the rest. If scrolling stops mid-window, the last position is never emitted. Enable `trailing` or use `auditTime`.
debounceTime is a lift door that closes only once nobody has stepped in for a few seconds; throttleTime is a turnstile that admits one person and then stays locked for a fixed time, whoever is waiting.
saying these in an interview costs you the question
- debounceTime emits a value every N milliseconds while the user types.
- throttleTime emits the latest value of each window by default.
- debounceTime and throttleTime are interchangeable for autosave.
- debounceTime emits the first value of a burst and ignores the rest.
- A throttled scroll tracker always receives the final scroll position.