The Mystery Behind RxJS iif
A common pattern in business logic: branch on a precondition to decide which API to call. For order payment, if it's a new order call the create-order endpoint; if the order already exists call the re-pay endpoint. The natural instinct is to reach for iif:
processOrder$
.pipe(
switchMap((isNewOrder) =>
iif(
() => isNewOrder === true,
from(fetch("/order")),
from(fetch("/pay")),
),
),
)
.subscribe();
Watching the network tab reveals something surprising — both requests fire, regardless of the condition.

Digging into iif
export function iif<T, F>(
condition: () => boolean,
trueResult: ObservableInput<T>,
falseResult: ObservableInput<F>,
): Observable<T | F> {
return defer(() => (condition() ? trueResult : falseResult));
}
iif is just a thin wrapper around defer. What does defer do?
export function defer<R extends ObservableInput<any>>(
observableFactory: () => R,
): Observable<ObservedValueOf<R>> {
return new Observable<ObservedValueOf<R>>((subscriber) => {
from(observableFactory()).subscribe(subscriber);
});
}
defer creates an Observable that defers subscription until the factory function is called at subscribe-time — similar to switchMap.
The bug: iif is a function. When you pass from(fetch("/order")) and from(fetch("/pay")) as arguments, JavaScript evaluates them before iif is even called. Both fetch calls fire immediately at argument-evaluation time; iif then just selects which already-fired Observable to subscribe to.
The Fix
The simplest fix: use a ternary or if/else to avoid eagerly evaluating the branch you don't need:
processOrder$.pipe(
switchMap((isNewOrder) =>
isNewOrder === true ?
from(fetch('/order')) :
from(fetch('/pay')),
)),
).subscribe();
If you want to keep iif for its expressiveness, wrap each branch in defer so the inner Observable is only created at subscribe-time:
of(true)
.pipe(
switchMap((result) =>
iif(
() => result === true,
defer(() => from(fetch("/pay"))),
defer(() => from(fetch("/order"))),
),
),
)
.subscribe();
Both defer calls are still invoked as iif arguments — but the from(fetch(...)) inside each is not executed until the defer Observable is subscribed to. Only the winning branch ever gets subscribed, so only one request fires.