RxJS
asynchronism
JavaScript
reactive programming
observable

How does RxJS create or simulate asynchronism?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

RxJS does not automatically make code asynchronous just because it uses observables. An observable can emit synchronously or asynchronously depending on its source and scheduler. What RxJS really provides is a uniform model for handling values over time, whether those values come from timers, events, Promises, or immediate in-memory data.

Observables Are Often Synchronous

A common misconception is that subscribe always means "later." It does not:

javascript
1import { of } from "rxjs";
2
3console.log("before");
4
5of(1, 2, 3).subscribe(value => {
6  console.log("value", value);
7});
8
9console.log("after");

This prints:

  1. before
  2. value 1
  3. value 2
  4. value 3
  5. after

The emissions happen synchronously because of can produce the values immediately during subscription.

Where Asynchronism Actually Comes From

RxJS usually works with sources that are already asynchronous in the JavaScript runtime:

  • DOM events
  • timers
  • Promises
  • AJAX requests
  • WebSocket messages

For example, interval uses the timer system:

javascript
1import { interval } from "rxjs";
2
3interval(1000).subscribe(value => {
4  console.log("tick", value);
5});

This is asynchronous because the browser or Node.js event loop schedules each timer callback later.

A Promise-backed observable is similar:

javascript
1import { from } from "rxjs";
2
3const promise = Promise.resolve("done");
4
5from(promise).subscribe(value => {
6  console.log(value);
7});

RxJS is not inventing the async behavior there. It is adapting an already-async source into the observable model.

Schedulers Can Delay Delivery

RxJS can also make delivery asynchronous by scheduling notifications. One common tool is observeOn(asyncScheduler):

javascript
1import { of, asyncScheduler } from "rxjs";
2import { observeOn } from "rxjs/operators";
3
4console.log("before");
5
6of(1, 2, 3)
7  .pipe(observeOn(asyncScheduler))
8  .subscribe(value => {
9    console.log("value", value);
10  });
11
12console.log("after");

Now after prints before the values because RxJS uses the async scheduler to defer the notifications.

This is often what people mean by RxJS "simulating" asynchronism. The values already exist, but RxJS can choose to deliver them later.

RxJS Is About Coordination, Not Threads

RxJS does not create operating-system threads for your JavaScript code. In typical browser or Node.js environments, the main model is still an event loop. RxJS helps structure time-based and event-based logic inside that model.

That is why operators such as map, filter, and scan transform streams, while operators such as switchMap, mergeMap, and concatMap coordinate asynchronous inner operations:

javascript
1import { fromEvent } from "rxjs";
2import { debounceTime, map, switchMap } from "rxjs/operators";
3import { ajax } from "rxjs/ajax";
4
5fromEvent(searchInput, "input")
6  .pipe(
7    debounceTime(300),
8    map(event => event.target.value),
9    switchMap(query =>
10      ajax.getJSON(`/api/search?q=${encodeURIComponent(query)}`)
11    )
12  )
13  .subscribe(results => {
14    console.log(results);
15  });

Here, RxJS is coordinating user input timing, cancellation behavior, and network results. The network request itself is still handled by the underlying runtime.

Cold Observables And Subscription Timing

Many observables are cold, which means the producer starts when you subscribe:

javascript
1import { Observable } from "rxjs";
2
3const source = new Observable(subscriber => {
4  console.log("producer started");
5  subscriber.next(1);
6  subscriber.complete();
7});
8
9source.subscribe(value => console.log(value));

Whether the producer behaves synchronously or asynchronously depends on what happens inside that function. RxJS gives you the contract between producer and subscriber, not a blanket promise about timing.

Common Pitfalls

The biggest mistake is assuming every observable is asynchronous. of, many custom observables, and several operators emit synchronously unless a scheduler or async source changes the timing.

Another common issue is confusing asynchronism with concurrency. RxJS coordinates event streams, but it does not automatically move CPU-heavy work off the main thread. If you need parallel computation, you need another runtime mechanism.

Developers also often reach for switchMap, mergeMap, and concatMap without understanding their different cancellation and ordering behavior. Those operators all handle async inner streams, but not in the same way.

Finally, schedulers change timing semantics. A stream that behaves fine synchronously may reveal race conditions or test failures once emissions are deferred.

Summary

  • RxJS observables can be synchronous or asynchronous.
  • Asynchronism usually comes from the underlying source, such as timers, Promises, events, or AJAX.
  • RxJS can also defer notifications with schedulers such as asyncScheduler.
  • The library is mainly about composing time-based flows, not creating threads.
  • Do not assume an observable is async unless the source or scheduler makes it so.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.