Observable vs Promise — What Is the Difference?
Both Observable and Promise are used to handle asynchronous operations in JavaScript/Angular, but they work differently.
The easiest distinction is:
Promise represents one future result. Observable represents a stream that can produce zero, one, or multiple values over time.
1. Full Simple Sample Program
First, compare them with simple examples.
Promise Example
const promise = new Promise<string>((resolve, reject) => {
setTimeout(() => {
resolve('Product loaded');
}, 2000);
});
promise.then(result => {
console.log(result);
});
After 2 seconds:
Product loaded
A Promise settles once.
Observable Example
import { Observable } from 'rxjs';
const observable$ = new Observable<number>(subscriber => {
subscriber.next(10);
subscriber.next(20);
subscriber.next(30);
subscriber.complete();
});
observable$.subscribe({
next: value => {
console.log(value);
},
error: error => {
console.error(error);
},
complete: () => {
console.log('Completed');
}
});
Output:
10
20
30
Completed
Here, the Observable emitted multiple values.
2. Main Difference
Promise
Start
│
│
└────────────► One Result
│
▼
Done
Observable:
Observable
Start
│
├────► Value 1
│
├────► Value 2
│
├────► Value 3
│
└────► Complete
So:
Promise → one eventual settlement
Observable → zero, one, or many values
3. Observable vs Promise Comparison
| Feature | Observable | Promise |
|---|---|---|
| Provided by | RxJS | JavaScript |
| Number of values | Zero, one, or many | One settlement |
| Execution | Usually lazy | Eager once created |
| Consumer API | subscribe() |
then() / await |
| Operators | Many RxJS operators | Limited built-in composition |
| Cancellation | Can unsubscribe | No general unsubscribe() |
| Streaming data | Excellent | Not designed for streams |
| Error handling | error, catchError() |
catch, try/catch |
| Retry | retry() |
Manual logic |
| Transformation | map(), filter(), etc. |
then() |
Angular HttpClient |
Native return type | Can be converted if needed |
Form valueChanges |
Excellent fit | Poor fit |
| WebSocket/events | Excellent fit | Poor fit |
4. Difference 1 — Single vs Multiple Values
Promise
A Promise settles only once.
const promise = new Promise<number>(resolve => {
resolve(10);
resolve(20);
resolve(30);
});
promise.then(value => {
console.log(value);
});
Output:
10
The later resolve() calls have no effect.
Conceptually:
Promise
10
│
▼
resolve
│
▼
Done
Observable
An Observable can emit multiple values.
import { of } from 'rxjs';
const numbers$ = of(10, 20, 30);
numbers$.subscribe(value => {
console.log(value);
});
Output:
10
20
30
Conceptually:
Observable
10 ─────►
20 ─────► Subscriber
30 ─────►
This is one of the most important differences.
5. Difference 2 — Eager vs Lazy
A Promise starts executing when it is created.
Promise
const promise = new Promise(resolve => {
console.log('Promise started');
resolve('Done');
});
console.log('Promise created');
Output:
Promise started
Promise created
Notice:
promise.then(...)
wasn't required to start the Promise executor.
Observable
Observables are generally lazy.
import { Observable } from 'rxjs';
const data$ = new Observable(subscriber => {
console.log('Observable started');
subscriber.next('Done');
});
console.log('Observable created');
At this point:
Observable created
The Observable producer hasn't run yet.
When:
data$.subscribe(value => {
console.log(value);
});
then:
Observable started
Done
So:
Promise
│
└── Starts when created
Observable
│
└── Usually starts when subscribed
This is particularly important with Angular HttpClient.
6. Difference 3 — then() vs subscribe()
Promise uses:
promise.then(result => {
console.log(result);
});
Or modern async/await:
const result = await promise;
Observable uses:
observable$.subscribe(result => {
console.log(result);
});
More completely:
observable$.subscribe({
next: value => {
console.log(value);
},
error: error => {
console.error(error);
},
complete: () => {
console.log('Completed');
}
});
Easy way to remember:
Promise → then() / await
Observable → subscribe()
7. Difference 4 — Cancellation
Observable subscriptions can be unsubscribed.
import { interval } from 'rxjs';
const timer$ = interval(1000);
const subscription = timer$.subscribe(value => {
console.log(value);
});
Later:
subscription.unsubscribe();
The subscriber stops receiving values.
0
1
2
3
unsubscribe()
STOP
Promises do not have a general:
promise.unsubscribe();
API.
Cancellation for Promise-based operations requires separate mechanisms where the underlying API supports them, such as AbortController with fetch().
8. Difference 5 — RxJS Operators
This is a major advantage of Observables in Angular.
Suppose we have:
import { of } from 'rxjs';
import { filter, map } from 'rxjs/operators';
const numbers$ = of(1, 2, 3, 4, 5);
We can write:
numbers$
.pipe(
filter(number => number > 2),
map(number => number * 10)
)
.subscribe(value => {
console.log(value);
});
Output:
30
40
50
Flow:
1 2 3 4 5
│
▼
filter(x > 2)
│
▼
3 4 5
│
▼
map(x * 10)
│
▼
30 40 50
RxJS provides many operators:
map()
filter()
tap()
switchMap()
mergeMap()
concatMap()
debounceTime()
distinctUntilChanged()
catchError()
retry()
take()
combineLatest()
forkJoin()
Promises have composition capabilities, but not this rich stream-processing operator model built in.
9. Difference 6 — Error Handling
Promise
Promise uses:
promise
.then(result => {
console.log(result);
})
.catch(error => {
console.error(error);
});
With async/await:
try {
const result = await promise;
} catch (error) {
console.error(error);
}
Observable
Observable can handle errors through the subscription:
observable$.subscribe({
next: value => {
console.log(value);
},
error: error => {
console.error(error);
}
});
Or using RxJS:
observable$
.pipe(
catchError(error => {
// handle error
return of([]);
})
);
This allows error handling to participate in the reactive pipeline.
10. Difference 7 — Retry
Suppose an HTTP request temporarily fails.
With RxJS:
this.http.get<Product[]>(url)
.pipe(
retry(2)
)
.subscribe(products => {
console.log(products);
});
Conceptually:
HTTP Request
│
▼
Failed
│
▼
Retry
│
▼
Failed
│
▼
Retry
│
▼
Success
With Promise-based code, retry behavior usually requires writing or using additional retry logic.
11. Difference 8 — Handling Streams
Suppose you have a search box.
The user types:
A
An
Ang
Angu
Angul
Angular
This is naturally a stream of values.
Angular Reactive Forms provides:
this.searchControl.valueChanges
which is an Observable.
You can then write:
this.searchControl.valueChanges
.pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap(search =>
this.productService.search(search)
)
)
.subscribe(products => {
this.products = products;
});
This is a natural Observable use case.
User types
│
▼
valueChanges
│
▼
debounceTime
│
▼
distinctUntilChanged
│
▼
switchMap
│
▼
Web API
A Promise is much less natural for representing the ongoing stream of search-box changes.
12. Angular HttpClient: Observable
Angular HttpClient returns Observables.
getProducts(): Observable<Product[]> {
return this.http.get<Product[]>(
'/api/products'
);
}
Component:
this.productService
.getProducts()
.subscribe(products => {
this.products = products;
});
For a normal HTTP request, the Observable typically behaves like:
Subscribe
│
▼
GET /api/products
│
▼
Server Response
│
▼
next(products)
│
▼
complete()
Although Observables can emit many values, a normal Angular HTTP request usually emits one response and completes.
13. Can We Convert Observable to Promise?
Yes.
RxJS provides:
firstValueFrom()
and:
lastValueFrom()
For example:
import { firstValueFrom } from 'rxjs';
async loadProducts() {
const products = await firstValueFrom(
this.http.get<Product[]>('/api/products')
);
console.log(products);
}
This lets you use:
await
when Promise-style control flow is preferable.
14. Can We Convert Promise to Observable?
Yes.
Use RxJS from().
import { from } from 'rxjs';
const promise = fetch('/api/products');
const response$ = from(promise);
response$.subscribe(response => {
console.log(response);
});
So conversion is possible in both directions.
15. When Should I Use Observable?
In Angular, prefer Observable when working naturally with reactive streams such as:
HttpClient
Reactive Forms valueChanges
Route parameters
WebSocket messages
User events
Timers
Search/autocomplete
Multiple dependent API calls
Shared application streams
Example:
searchControl.valueChanges
.pipe(
debounceTime(300),
switchMap(search => this.search(search))
);
Observable is especially powerful when data changes repeatedly over time.
16. When Is Promise Useful?
Promise is useful when you have a straightforward asynchronous operation with one eventual result and want simple async/await control flow.
For example:
async function loadData() {
const response = await fetch('/api/products');
const products = await response.json();
console.log(products);
}
This style can be simple and readable for sequential one-result operations.
17. Very Important Interview Distinction
Don't say:
"Observable is asynchronous and Promise is synchronous."
That is incorrect.
Both are commonly used for asynchronous operations.
The important differences are:
Promise
├── one settlement
├── eager once created
└── then() / await
Observable
├── zero, one, or many values
├── usually lazy
├── subscribe()
├── unsubscribe()
└── powerful RxJS operators
18. Easy Real-World Analogy
Think about ordering food online.
Promise
You place one order.
Place Order
│
▼
Waiting...
│
▼
Food Delivered
One eventual outcome.
Observable
Think of tracking the order status:
Order placed
↓
Restaurant accepted
↓
Preparing
↓
Picked up
↓
Driver nearby
↓
Delivered
You're receiving multiple updates over time.
That is closer to an Observable stream.
Key Points
- Promise is built into JavaScript.
- Observable comes from RxJS.
- Promise settles once with a value or error.
- Observable can emit zero, one, or multiple values.
- Promise uses
then()/catch()orasync/await. - Observable uses
subscribe(). - Promises are eager once created.
- Observables are generally lazy.
- Observable subscriptions can be unsubscribed.
- RxJS provides powerful operators such as
map,filter,switchMap,retry, andcatchError. - Observables are well suited for continuous data streams.
- Angular
HttpClientreturns Observables. - A normal HTTP Observable usually emits one response and completes.
- Observables can be converted to Promises with
firstValueFrom()when appropriate.
Interview Questions and Answers
1. What is the main difference between Observable and Promise?
Answer:
A Promise represents one eventual settlement, whereas an Observable can represent zero, one, or multiple values over time.
2. Which one is lazy?
Answer:
Observables are generally lazy and usually begin execution when subscribed. A Promise's executor starts when the Promise is created.
3. Can Promise return multiple values?
Answer:
No. A Promise settles once.
An Observable can emit multiple values:
of(10, 20, 30)
4. Can we cancel an Observable?
Answer:
An active Observable subscription can be stopped using:
subscription.unsubscribe();
Promises do not provide a general unsubscribe() mechanism.
5. Which one does Angular HttpClient return?
Answer:
Angular HttpClient returns:
Observable<T>
For example:
Observable<Product[]>
6. Can Observable be converted to Promise?
Answer:
Yes. For example:
const products = await firstValueFrom(
this.http.get<Product[]>(url)
);
7. Which is better: Observable or Promise?
Answer:
Neither is universally better. Promise is simple for one-result asynchronous operations, while Observable is more suitable for reactive streams, cancellation/unsubscription, and complex asynchronous composition.
8. Why are Observables useful for search?
Answer:
Search input produces multiple values over time. Operators such as:
debounceTime()
distinctUntilChanged()
switchMap()
make it easy to control requests and switch to the latest search.
9. Does an HTTP Observable emit multiple responses?
Answer:
A normal Angular HttpClient request typically emits one response and then completes, even though the Observable abstraction itself supports multiple emissions.
10. Short Interview Answer
A Promise represents one future result and is eager once created, while an Observable represents a lazy stream that can emit zero, one, or multiple values and supports RxJS operators, composition, and unsubscription.