RxJS Fundamentals: Thinking in Streams
When I first looked at RxJS, it felt overwhelming. There were so many new terms: Observables, Operators, Subscriptions… But then I realized the secret: think in streams.
Related articles
See all


When I first looked at RxJS, it felt overwhelming. There were so many new terms: Observables, Operators, Subscriptions… But then I realized the secret: think in streams.


Once you adopt this mindset, RxJS stops being scary and starts being powerful.
This article will introduce you to the foundations of RxJS in a simple way. No jargon, no overcomplication just the core ideas, explained with examples you can run in your browser.
Modern front-end apps deal with a lot of asynchronous events:
A user typing in a search box
An API request that may succeed or fail
A button that can be clicked multiple times
A timer running in the background
Normally, we write messy code with callbacks or promises to handle these. Very quickly, things become spaghetti:
How do you cancel an API request if the user types something new?
How do you combine a timer with button clicks?
How do you retry when something fails?
That’s where RxJS comes in.
It allows you to treat everything as a stream of data and then transform, combine, or control those streams with powerful operators.
A common question: What’s the difference between an Observable and a Promise?
Here’s the simplest explanation:
Promise: Think of it as ordering one pizza. You call, wait, and eventually get one delivery. After that, it’s over.
Observable: Imagine subscribing to a pizza subscription service. Every Friday, a new pizza arrives at your door. You can keep receiving them, or cancel your subscription whenever you want.
// Promise -> delivers one valueconst promise = fetch('/api/data');promise.then(data => console.log('Got data:', data));// Observable -> can deliver many values over timeimport { interval } from 'rxjs';const numbers$ = interval(1000); // emits 0, 1, 2, 3, ...numbers$.subscribe(value => console.log('Tick:', value));
A Promise is a one time event.
An Observable is a continuous stream of events.
This is the mental shift:
Everything in your app can be thought of as a stream.
Clicks are a stream.
Mouse moves are a stream.
API responses are a stream.
Even a static number can be wrapped into a stream.
import { fromEvent } from 'rxjs';
const clicks$ = fromEvent(document, 'click');
clicks$.subscribe(event =>
console.log('Clicked at:', event.clientX, event.clientY)
);Every time you click, the stream emits a new value.
Let’s break down the three most important words in RxJS:
The data source. It defines what kind of values it will produce and when.
The instructions for what to do when new values arrive.
It has three parts:
next: what to do with each value
error: what to do if something goes wrong
complete : what to do when the stream finishes
The active connection between an Observable and an Observer.
You can cancel it when you no longer need it.
import { Observable } from 'rxjs';
const obs$ = new Observable(subscriber => {
subscriber.next('Hello');
subscriber.next('World');
subscriber.complete();
});
obs$.subscribe({
next: value => console.log(value),
error: err => console.error('Error:', err),
complete: () => console.log('Done!')
});
// Output:
// Hello
// World
// Done!Notice how similar it feels to “subscribing” to a newsletter:
Every new article is a next value.
If the newsletter stops, that’s a complete.
If something goes wrong with delivery, that’s an error.
RxJS is not just a library it’s a new way of thinking.
Instead of writing imperative “do this, then that code”, you model your app as streams that evolve over time.
Promises give you one value.
Observables can give you many values over time.
Subscriptions let you start or stop listening whenever you want.
This mindset shift thinking in streams is the foundation of reactive programming.