← Back to Articles         
forkJoin(), combineLatest(), Subject, and BehaviorSubject

forkJoin(), combineLatest(), Subject, and BehaviorSubject

Published on 01 Oct 2026     11 min read Angular
RxJS & Observables

forkJoin(), combineLatest(), Subject, and BehaviorSubject

These are important RxJS concepts in Angular, but they solve two different kinds of problems:

Concept Main purpose
forkJoin() Wait for multiple Observables to complete, then return their final values
combineLatest() Combine the latest values whenever any source changes
Subject Manually emit values to multiple subscribers
BehaviorSubject Subject that also stores the latest/current value

1. What is forkJoin()?

What is it?

forkJoin() combines multiple Observables and waits until all of them complete.

After all complete, it emits one combined result containing the latest/final value from each Observable, then completes.

forkJoin() = Run multiple Observables → wait for all to complete → emit one combined result.

It is particularly useful for multiple independent HTTP requests.


Full Simple Angular Example

dashboard.service.ts

import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { forkJoin } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class DashboardService {

  private http = inject(HttpClient);

  getDashboardData() {

    return forkJoin({

      products:
        this.http.get('/api/products'),

      customers:
        this.http.get('/api/customers'),

      orders:
        this.http.get('/api/orders')

    });
  }
}

Component

import { Component, inject } from '@angular/core';
import { DashboardService } from './dashboard.service';

@Component({
  selector: 'app-dashboard',
  standalone: true,
  template: `
    <h2>Dashboard</h2>
  `
})
export class Dashboard {

  private dashboardService = inject(DashboardService);

  constructor() {

    this.dashboardService
      .getDashboardData()
      .subscribe(result => {

        console.log('Products:', result.products);
        console.log('Customers:', result.customers);
        console.log('Orders:', result.orders);

      });
  }
}

How forkJoin() Works

Suppose:

Products API  ───────────► Complete
Customers API ─────► Complete
Orders API    ─────────────────► Complete
                                  │
                                  ▼
                              forkJoin()
                                  │
                                  ▼
                         Combined Result

Even if Products completes first, forkJoin() waits for Orders and Customers.

Then it emits:

{
  products: ...,
  customers: ...,
  orders: ...
}

Important Point

forkJoin() waits for completion.

If one source never completes:

forkJoin({
  api: this.http.get('/api/products'),
  timer: interval(1000)
})

interval() never completes by itself.

Therefore forkJoin() never emits.

That's why forkJoin() is an excellent fit for normal HttpClient requests—they normally emit a response and complete.


2. What is combineLatest()?

What is it?

combineLatest() combines multiple Observables and emits whenever any source Observable emits a new value, using the latest value from every source.

combineLatest() = Whenever something changes, give me the latest values from all sources.


Full Simple Example

import { combineLatest, Subject } from 'rxjs';

const category$ = new Subject<string>();
const search$ = new Subject<string>();

combineLatest([
  category$,
  search$
])
.subscribe(([category, search]) => {

  console.log(
    'Category:', category,
    'Search:', search
  );

});

category$.next('Laptop');

search$.next('Dell');

search$.next('HP');

category$.next('Mobile');

Output

Category: Laptop Search: Dell

Category: Laptop Search: HP

Category: Mobile Search: HP

How Does It Work?

First:

category$.next('Laptop');

At this point:

Category = Laptop
Search   = ???

No output yet because search$ hasn't emitted.

Then:

search$.next('Dell');

Now both have emitted:

Category = Laptop
Search   = Dell

combineLatest() emits:

[Laptop, Dell]

Then:

search$.next('HP');

It remembers:

Category = Laptop

and combines it with:

Search = HP

Result:

[Laptop, HP]

Important Rule of combineLatest()

combineLatest() normally waits until every source has emitted at least once.

Observable A ─── A1 ───────── A2 ──────
Observable B ───────── B1 ───────── B2 ─
                     │
                     ▼
               First combined
                  result

Before B1, there is no latest value for B.

Therefore no combined value can be produced yet.


3. forkJoin() vs combineLatest()

This difference is extremely important.

forkJoin()

API 1 ─────────────── Complete
API 2 ─────── Complete
API 3 ─────────────────── Complete
                           │
                           ▼
                     ONE RESULT

Waits for all to complete.


combineLatest()

A ── A1 ───── A2 ───────── A3 ──
B ───── B1 ───────── B2 ─────────
        │       │     │     │
        ▼       ▼     ▼     ▼
     Combined Combined Combined...

Keeps producing combined values when sources change.


Comparison

Feature forkJoin() combineLatest()
Waits for all sources to emit Yes Yes, initially
Waits for all to complete Yes No
Number of combined emissions Usually one Potentially many
Uses latest/final values Final values at completion Latest current values
Good for multiple HTTP calls Excellent Sometimes
Good for changing streams No Excellent
Long-lived Observables Usually unsuitable Suitable

Easy memory rule:

forkJoin()
    → Wait until everything FINISHES

combineLatest()
    → React whenever something CHANGES

4. What is a Subject?

What is it?

A Subject is a special type of RxJS Observable that allows us to manually emit values using next() and broadcast those values to multiple subscribers.

A normal Observable is usually consumed like:

observable$.subscribe(...)

With a Subject, you can also produce values:

subject.next(...)

Therefore a Subject is both:

Observable
   +
Observer

Full Simple Example

import { Subject } from 'rxjs';

const message$ = new Subject<string>();

message$.subscribe(message => {

  console.log('Subscriber 1:', message);

});

message$.subscribe(message => {

  console.log('Subscriber 2:', message);

});

message$.next('Hello');

message$.next('Angular');

Output

Subscriber 1: Hello
Subscriber 2: Hello

Subscriber 1: Angular
Subscriber 2: Angular

How Does Subject Work?

message$.next('Hello');

broadcasts:

                    ┌────► Subscriber 1
                    │
Subject ── Hello ───┼────► Subscriber 2
                    │
                    └────► Subscriber 3

All currently subscribed subscribers receive the value.

This is called multicasting.


Subject Does Not Store the Previous Value

This is very important.

const subject = new Subject<string>();

subject.next('Angular');

subject.subscribe(value => {
  console.log(value);
});

What is printed?

Nothing

Why?

Because:

Angular emitted
      │
      ▼
No subscriber yet
      │
      ▼
Value missed

Then the subscriber arrives later.

A plain Subject does not automatically replay the previous value.


5. What is a BehaviorSubject?

What is it?

A BehaviorSubject is a special type of Subject that:

  1. requires an initial value,
  2. stores the latest value,
  3. immediately gives the current/latest value to a new subscriber.

BehaviorSubject = Subject + current/latest value.


Full Simple Example

import { BehaviorSubject } from 'rxjs';

const userName$ =
  new BehaviorSubject<string>('Guest');

userName$.subscribe(value => {

  console.log('Subscriber 1:', value);

});

userName$.next('Syed');

userName$.subscribe(value => {

  console.log('Subscriber 2:', value);

});

userName$.next('Admin');

Output

Subscriber 1: Guest

Subscriber 1: Syed

Subscriber 2: Syed

Subscriber 1: Admin
Subscriber 2: Admin

Why Did Subscriber 2 Receive Syed?

Because BehaviorSubject remembers the latest value.

Flow:

Initial value
   Guest
     │
     ▼

Subscriber 1
     │
     ▼
   Guest


next("Syed")
     │
     ▼
Latest value = Syed
     │
     ├────────► Subscriber 1
     │
     ▼

Subscriber 2 joins
     │
     ▼
Immediately receives
     │
     ▼
   "Syed"

That's the key feature.


6. Why Does BehaviorSubject Require an Initial Value?

You must create it like:

const user$ =
  new BehaviorSubject<string>('Guest');

Because a BehaviorSubject always represents a current value.

At the beginning, that current value is:

Guest

Later:

user$.next('Syed');

Current value becomes:

Syed

Later:

user$.next('Admin');

Current value becomes:

Admin

Think:

BehaviorSubject

Initial → Guest
           ↓
next()  → Syed
           ↓
next()  → Admin
           ↓
Current → Admin

7. Real Angular BehaviorSubject Example

A common use is sharing state through a service.

user.service.ts

import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class UserService {

  private userSubject =
    new BehaviorSubject<string>('Guest');

  user$ =
    this.userSubject.asObservable();

  setUser(userName: string): void {

    this.userSubject.next(userName);

  }
}

Component A updates the user:

this.userService.setUser('Syed');

Component B listens:

this.userService.user$
  .subscribe(user => {

    console.log(user);

  });

Flow:

Component A
    │
    │ setUser("Syed")
    ▼
UserService
    │
    ▼
BehaviorSubject
    │
    │ next("Syed")
    ▼
user$
    │
    ▼
Component B

This is one way to share reactive state between components.


8. Why Use asObservable()?

Notice this pattern:

private userSubject =
  new BehaviorSubject<string>('Guest');

user$ =
  this.userSubject.asObservable();

We keep the actual Subject private:

private userSubject

because we don't want components doing:

this.userService.userSubject.next(...)

Instead, the service controls updates:

setUser(userName: string) {

  this.userSubject.next(userName);

}

Consumers only receive:

user$

Conceptually:

             UserService

     private BehaviorSubject
               │
       ┌───────┴────────┐
       │                │
    next()          asObservable()
       │                │
Service controls      Components
 updates             can subscribe

This gives better encapsulation.


9. Getting the Current Value from BehaviorSubject

BehaviorSubject also exposes its current value.

For example:

const userSubject =
  new BehaviorSubject<string>('Guest');

console.log(userSubject.value);

Output:

Guest

Then:

userSubject.next('Syed');

console.log(userSubject.value);

Output:

Syed

You may also encounter:

userSubject.getValue();

However, in reactive Angular code, it is often cleaner to consume the exposed Observable rather than frequently reading the Subject imperatively.


10. Subject vs BehaviorSubject

This is one of the most common RxJS interview questions.

Subject

const subject =
  new Subject<string>();

No initial value.

Doesn't give a previous value to a late subscriber.


BehaviorSubject

const subject =
  new BehaviorSubject<string>('Guest');

Requires an initial value.

Stores the latest value and immediately emits it to new subscribers.


Subject vs BehaviorSubject Example

Subject

const subject =
  new Subject<string>();

subject.next('Angular');

subject.subscribe(value => {
  console.log(value);
});

Output:

Nothing

The subscriber arrived too late.


BehaviorSubject

const subject =
  new BehaviorSubject<string>('Initial');

subject.next('Angular');

subject.subscribe(value => {
  console.log(value);
});

Output:

Angular

Because BehaviorSubject remembered:

Latest value = Angular

11. Subject vs BehaviorSubject Comparison

Feature Subject BehaviorSubject
Initial value required No Yes
Stores latest value No Yes
Late subscriber gets latest value No Yes
Supports next() Yes Yes
Multiple subscribers Yes Yes
Multicast Yes Yes
Current value available No Yes
Good for events Yes Possible
Good for current state Less suitable Yes

12. When Should You Use Subject?

Use a Subject when the information represents an event, where new subscribers do not need the previous event.

For example:

Button clicked
Refresh requested
Modal closed
Notification event
Manual trigger

Imagine:

Doorbell rings

If you arrive five minutes later, you don't necessarily need the old ring.

That's similar to:

Subject

13. When Should You Use BehaviorSubject?

Use a BehaviorSubject when the information represents current state.

Examples:

Current user
Current selected language
Current theme
Current cart state
Current filter
Current application status

For example:

Current logged-in user = Syed

A component created later may still need to know the current user.

That's a good conceptual fit for:

BehaviorSubject

14. Easy Real-World Analogy

Subject = Live Radio

If you start listening at 10:30:

10:00 → Song A
10:10 → Song B
10:20 → Song C

10:30 → You start listening

You don't automatically hear Song A, B, or C.

You only receive future broadcasts.

Subject → future emissions

BehaviorSubject = Current Scoreboard

Imagine:

India: 250/4

You enter the stadium now.

You immediately see:

India: 250/4

Then it changes:

251/4
252/4
253/4

You continue receiving updates.

That's similar to:

BehaviorSubject

It provides:

Current/latest value
+
Future updates

15. forkJoin() vs combineLatest() vs Subjects

Don't confuse these concepts.

forkJoin() and combineLatest() combine Observables.

Observable A ─┐
              ├──► forkJoin/combineLatest
Observable B ─┘

Subject and BehaviorSubject are themselves Observable-like producers that you can push values into:

next(value)
    │
    ▼
Subject
    │
    ├────► Subscriber A
    └────► Subscriber B

So:

forkJoin       → Combine completed results
combineLatest  → Combine changing values

Subject        → Broadcast events
BehaviorSubject→ Broadcast + remember current value

Key Points

  • forkJoin() waits for all source Observables to complete and then emits one combined result.
  • forkJoin() is very useful for multiple independent HttpClient requests.
  • If a source never completes, forkJoin() may never emit.
  • combineLatest() emits whenever a source changes, after every source has emitted at least once.
  • combineLatest() remembers the latest value from each source.
  • Subject allows values to be manually emitted with next().
  • A Subject multicasts values to its current subscribers.
  • A plain Subject does not store the previous value for late subscribers.
  • BehaviorSubject requires an initial value.
  • BehaviorSubject stores the latest value.
  • A new BehaviorSubject subscriber immediately receives the latest value.
  • Subject is commonly appropriate for events.
  • BehaviorSubject is commonly appropriate for current state.
  • Keeping the Subject private and exposing asObservable() is a common service encapsulation pattern.

Easy memory rule

forkJoin()
    → Wait until ALL FINISH

combineLatest()
    → React when values CHANGE

Subject
    → EVENT broadcaster

BehaviorSubject
    → CURRENT VALUE + future updates

Interview Questions and Answers

1. What is forkJoin()?

Answer:
forkJoin() combines multiple Observables, waits until all complete, then emits one result containing the latest/final value from each source.


2. Why is forkJoin() useful with Angular HttpClient?

Answer:
Normal HTTP Observables emit their response and complete, so forkJoin() can run multiple independent API calls and provide their results together after all have completed.


3. What happens if one forkJoin() source never completes?

Answer:
forkJoin() will not emit its combined result because it waits for all sources to complete.


4. What is combineLatest()?

Answer:
combineLatest() combines the latest values from multiple Observables and emits a new combined result whenever any source emits, after every source has emitted at least once.


5. forkJoin() vs combineLatest()?

Answer:

forkJoin
→ Wait for all to COMPLETE
→ Usually one combined emission

combineLatest
→ Wait for all to EMIT initially
→ Then emit whenever a source changes

6. What is a Subject?

Answer:
A Subject is a special RxJS Observable that can manually emit values using next() and multicast those values to multiple current subscribers.


7. What is a BehaviorSubject?

Answer:
A BehaviorSubject is a Subject that requires an initial value, stores the latest value, and immediately provides that latest value to new subscribers.


8. What is the main difference between Subject and BehaviorSubject?

Answer:

Subject
→ No initial value
→ Doesn't store latest value

BehaviorSubject
→ Requires initial value
→ Stores latest value
→ Gives latest value to new subscribers

9. When should you use Subject?

Answer:
A Subject is suitable for event-like data where only future emissions matter, such as refresh triggers or notification events.


10. When should you use BehaviorSubject?

Answer:
A BehaviorSubject is suitable when you need to represent current state and new subscribers should immediately receive the latest value.


11. Why expose asObservable() from a service?

Answer:
It prevents consumers from calling next() directly. The service retains control over state changes while components can only subscribe.

private userSubject =
  new BehaviorSubject<string>('Guest');

user$ = this.userSubject.asObservable();

12. Short Interview Answer

forkJoin waits for multiple Observables to complete and returns their final values; combineLatest continuously combines their latest values. A Subject broadcasts new values to current subscribers, while a BehaviorSubject additionally stores the current/latest value and immediately provides it to new subscribers.

← Back to Articles