In RxJS, which creation functions emit synchronously inside subscribe(), and what bugs can that synchronous emission cause in real code?
answer
- values before subscribe() returns
- of, from(array), range, EMPTY
- promises and timers are later
- tests with of() hide timing
basics
~20 sof, from over arrays or iterables, range and EMPTY deliver all values and completion before subscribe() returns; from(promise), interval and timer deliver later. Synchronous delivery breaks code that uses the subscription variable in its callback or that assumes asynchrony.
solid answer
~40 s`of`, `from(array)`, `from(iterable)`, `range` and `EMPTY` emit every value and complete **during** the `subscribe()` call. `from(promise)`, `from(asyncIterable)`, `interval` and `timer` - even `timer(0)` - emit later; `fromEvent` emits when the event fires; `defer` and `iif` inherit the timing of what they return; `NEVER` emits nothing. The bugs: a callback that calls `sub.unsubscribe()` on `const sub = src$.subscribe(...)` hits the variable before it is assigned; tests that mock an asynchronous API with `of(data)` pass while the real, asynchronous code has a race; a large synchronous source such as `range(0, 1e6)` blocks the thread for the whole run. Fix the code so it does not depend on timing, or make emission asynchronous with `scheduled(input, asyncScheduler)`.
go deeper
Recall that of, from over an array and range emit synchronously, while promises and timers deliver later.
Place each creation function in the synchronous or asynchronous column, including the from(resolved promise) and timer(0) surprises.
Diagnose timing bugs caused by synchronous sources: subscription variables used in callbacks, over-synchronous test mocks and long synchronous pipelines blocking the thread.
Push the codebase toward timing-independent subscribers and asynchronous test doubles, so no behaviour depends on whether a source happens to be synchronous.
## Observables are not inherently asynchronous An RxJS Observable delivers values **whenever its producer produces them**. If the producer loops over an array inside the subscribe logic, every value - and the completion - reaches the observer before `subscribe()` returns. Knowing which creation functions do this is a common interview check, because many subtle bugs come from assuming the opposite. ## Which functions are synchronous | Creation function | Timing of values | Completes | |---|---|---| | `of(a, b, c)` | synchronous, inside `subscribe()` | synchronously | | `from(array)`, `from(iterable)`, `from('abc')` | synchronous | synchronously | | `range(start, count)` | synchronous | synchronously | | `EMPTY` | none | synchronously | | `from(promise)` | after the promise's reaction runs | then | | `from(asyncIterable)`, `from(readableStream)` | asynchronous | when exhausted | | `interval(p)`, `timer(d)`, `timer(0)` | asynchronous, on timers | `timer(d)` only | | `fromEvent(target, name)` | when the event fires | never | | `NEVER` | never | never | | `defer(f)`, `iif(...)` | same as the source they return | same | Two entries surprise people. `from(Promise.resolve(x))` is asynchronous even though the promise has already resolved, because promise reactions always run after the current synchronous code. And `timer(0)` is asynchronous: a zero delay still goes through a timer. ## Bug 1: using the subscription inside its own callback ```ts const sub = of(1, 2, 3).subscribe((v) => { if (v === 2) sub.unsubscribe(); }); ``` The callback runs while `subscribe()` is still executing, before `sub` has been assigned. With `const`, reading `sub` throws a `ReferenceError`; RxJS catches exceptions thrown in observer callbacks and reports them asynchronously as unhandled errors, so the failure appears later, away from this line. With the same code over an asynchronous source, `sub` is assigned in time and the bug stays hidden. The fix is an operator that ends the stream, such as `take(2)` or `takeWhile`, rather than reaching for the subscription. ## Bug 2: tests that are more synchronous than production A service that normally returns an asynchronous HTTP response is often mocked in a unit test with `of(fakeData)`. The test now runs synchronously: state set in the subscribe callback is visible on the very next line, and a loading flag set before the call is cleared before any assertion could see it. Production, where the response arrives later, may show a race the test never exercised - a spinner that flickers, or code that reads a field before it is populated. Making the mock asynchronous exposes the same timing as production: - `from(Promise.resolve(fakeData))` delivers after the current synchronous code; - `scheduled([fakeData], asyncScheduler)` delivers on a timer; - marble tests with virtual time give precise control, a separate topic. ## Bug 3: blocking the thread `range(0, 1_000_000).pipe(...)` runs its whole pipeline inside `subscribe()`. Nothing else - rendering, input handling - can happen until it finishes. The array and iterable loops check `subscriber.closed` before each value, so a downstream `take` stops them early, but a pipeline that consumes everything will not yield. ## Bug 4: completion before setup finishes `EMPTY.subscribe({ complete: done })` calls `done` before `subscribe()` returns. Code that registers state **after** subscribing, expecting completion to come later, runs its cleanup first and its setup second. ## Making emission asynchronous when you must The scheduler argument on `of` and `from` - `of(1, 2, asyncScheduler)` - is deprecated since RxJS 6.5 and will be removed in v8. The current replacement is the creation function **`scheduled(input, scheduler)`**, for example `scheduled([1, 2], asyncScheduler)`. Moving delivery onto a scheduler part-way through a pipeline, and the differences between schedulers, belong to the scheduler topic. The better fix is usually structural: write subscribers that work whatever the timing, and end streams with operators rather than with a subscription reference. ## Quick rules to remember - Arrays, iterables, strings, `of` and `range`: synchronous. - Promises, even resolved ones: after the current synchronous code. - `interval` and `timer`, even with zero delay: on timers, asynchronous. - Events: whenever they fire. - `EMPTY` completes synchronously; `NEVER` does nothing, ever. - `defer` and `iif`: whatever the chosen source does. ## What to say Name the synchronous functions, note that promises and timers are always asynchronous, and give one concrete bug - the subscription-in-callback case or the over-synchronous test mock - with its fix.
- In RxJS, why is from(Promise.resolve(1)) asynchronous even though the promise is already resolved?RxJS subscribes by calling the promise's `then`, and promise reactions are always queued to run after the current synchronous code, whether or not the promise has settled. So the value arrives after `subscribe()` returns. RxJS adds no delay of its own; the timing comes entirely from how promises deliver results.
- In RxJS, why is take(2) better than calling sub.unsubscribe() inside the next callback?`take(2)` completes and unsubscribes from inside the pipeline, so it works for synchronous and asynchronous sources alike and does not need the subscription variable. Calling `sub.unsubscribe()` in the callback breaks on synchronous sources, where `sub` is not yet assigned when the callback runs, and it skips the completion signal that downstream code might rely on.
saying these in an interview costs you the question
- All RxJS Observables emit asynchronously, so values arrive after subscribe() returns.
- timer(0) emits synchronously because its delay is zero.
- from(Promise.resolve(x)) is synchronous because the promise has already resolved.
- Passing asyncScheduler as the last argument to of() is the current recommended API.
- Mocking an HTTP call with of(data) reproduces production timing in unit tests.