skip to content

In RxDart, what do throttleTime's leading and trailing defaults do, and why would a throttled movie search miss the final query?

level: middleimportance: should knowfreq 34%

answer

  1. window opens on an emitted event
  2. leading true, trailing false
  3. the last value in a window is dropped
  4. debounce waits for silence
  5. trailing: true emits at window end

basics

~20 s

throttleTime defaults to leading: true and trailing: false: it emits the first event, then drops everything else for the duration. Typing "dune" quickly searches for "d" and never for "dune"; search needs debounceTime or trailing: true.

solid answer

~40 s

`throttleTime(duration, {trailing = false, leading = true})` emits an event, then ignores further events until the window of `duration` closes. With the defaults it keeps the **first** event of each burst and drops the rest — including the last one. For a search box that is the wrong half: typing "dune" within the window sends "d" and discards "du", "dun" and "dune". Setting `trailing: true` also emits the last event of each window when it closes, and `leading: false, trailing: true` emits only the last. For search the usual answer is still `debounceTime`, which waits until events stop for the whole duration and then emits the latest. Throttle fits sources where regular samples are good enough — a scroll position that triggers "load more" results, for instance.

code

dart · 7 lines
dart
import 'package:rxdart/rxdart.dart';

// Load the next page of movie results at most once per second
// while the user keeps scrolling near the end of the list.
Stream<void> loadMoreTriggers(Stream<double> distanceToEnd) => distanceToEnd
    .where((px) => px < 400)
    .throttleTime(const Duration(seconds: 1));

go deeper

for a junior

Remember that debounceTime waits for a pause and emits the last value, while throttleTime by default emits the first value and drops the rest of the window.

for a middle

Explain the leading and trailing flags, their defaults, and trace a fast burst of keystrokes through each configuration.

for a senior

Match the operator to the failure that matters — losing the final value versus reacting late — and choose throttle for load-more triggers and debounce for search.

for a principal

Consider where rate limiting belongs — UI input, the repository or the server — and keep one consistent policy so behaviour does not differ screen by screen.

## Two rate limiters with different promises `rxdart` provides two time-based rate limiters as extension methods on `Stream`: - **`debounceTime(Duration duration)`** — every event restarts a timer; an event is emitted only when `duration` passes without another event. It promises the **last** value of a burst, delivered after the burst ends. - **`throttleTime(Duration duration, {bool trailing = false, bool leading = true})`** — an emitted event opens a window of `duration`; events arriving inside the window are handled according to `leading` and `trailing`. It promises a bounded **rate**, not the last value. ## The throttleTime flags | `leading` | `trailing` | What each window emits | |---|---|---| | `true` (default) | `false` (default) | the first event; the rest are dropped | | `false` | `true` | the last event, when the window closes | | `true` | `true` | the first event immediately, and the last one when the window closes | The default pairing is the classic "throttle first": fast reaction to the first event, then silence. With `trailing: true` rxdart keeps the newest event seen during the window and emits it when the window ends, which is how you avoid losing the final value. ## Why a throttled search misses "dune" Suppose `throttleTime(const Duration(milliseconds: 500))` with the defaults, and the user types four characters 100 ms apart: 1. "d" arrives — no window is open, so it is **emitted** and a 500 ms window opens. 2. "du", "dun" and "dune" arrive inside the window and are **dropped**. 3. The window closes. With `trailing: false`, nothing is emitted. 4. No further keystrokes arrive, so no new window opens. The app searches for "d" and shows a list that has nothing to do with "Dune". Adding `trailing: true` fixes the lost value but still fires an early, useless search for "d". `debounceTime(const Duration(milliseconds: 300))` fires once, for "dune", 300 ms after the last keystroke — the behaviour a search box wants. ## Where throttleTime is the right tool - **Scroll-driven "load more"** on the results list: the first scroll event near the end should trigger immediately, and repeats within the window should not trigger again. - **A tap that must react instantly** but not be spammed, where dropping the extra taps is fine. - **High-frequency sensor or position updates** where one sample per window is enough. A useful test for choosing: if losing the **final** value is a bug, use `debounceTime` or `throttleTime(..., trailing: true)`; if **reacting late** is the bug, keep the leading edge. ## Completion behaviour Both operators are built on rxdart's backpressure transformer. When the source completes, `debounceTime` emits any value still waiting for its timer right away instead of dropping it, then completes. That is why `Stream.fromIterable([1, 2, 3, 4]).debounceTime(const Duration(seconds: 1))` prints `4`. ## Related operators `throttle` and `debounce` take a window-stream factory instead of a `Duration`, for windows that depend on the event; `sampleTime` emits the latest value at fixed intervals. For interview purposes, knowing the two `...Time` forms and the throttle defaults is what is asked. ```dart import 'package:rxdart/rxdart.dart'; Future<void> main() async { Stream<String> typing() => Stream.periodic( const Duration(milliseconds: 100), (i) => 'dune'.substring(0, i + 1), ).take(4); const window = Duration(milliseconds: 500); print(await typing().throttleTime(window).toList()); // [d] print(await typing().throttleTime(window, trailing: true).toList()); // [d, dune] print(await typing().debounceTime(const Duration(milliseconds: 300)).toList()); // [dune] } ```

  • What does throttleTime(window, leading: true, trailing: true) emit for a burst of four events inside one window?
    It emits the first event immediately and the last event of the burst when the window closes, so two of the four get through. That keeps the fast first reaction and still delivers the final value, at the cost of one extra emission compared with debouncing.
  • The source completes while debounceTime is still waiting. Is the pending value lost?
    No. In rxdart the pending value is emitted as soon as the source completes, and then the output completes. That is why `Stream.fromIterable([1, 2, 3, 4]).debounceTime(const Duration(seconds: 1))` prints `4` rather than nothing.

saying these in an interview costs you the question

  • throttleTime emits the last value of each window by default.
  • throttleTime and debounceTime both guarantee the final value is delivered.
  • debounceTime drops the pending value when the source completes.
  • Setting trailing: true on throttleTime makes it behave exactly like debounceTime.
  • Throttling is the standard choice for search-as-you-type.