Skip to the solution
MediumFrontend10 min read

What is the difference between take(1) and first() operators in RxJS?

410 views

The problem

Question presented to candidate: "In a route guard we read the current user with this.auth.user$.pipe(first()), and in a test it blew up with EmptyError: no elements in sequence. A colleague says take(1) would have been fine. What is the difference between take(1) and first(), and which one would you use where?"

What a strong answer should cover:

  • Both take the first value, complete, and unsubscribe from the source immediately.
  • The difference is the empty case: if the source completes without emitting, take(1) just completes, while first() errors with EmptyError ("no elements in sequence").
  • first() also accepts a predicate and a default value: first(pred) is filter(pred) + take(1) + an EmptyError if nothing matched; first(pred, defaultValue) emits the default instead of erroring.
  • Choose by intent: first() when "no value" is a bug you want to surface; take(1) when "no value" is acceptable.
  • Strong candidates know the guard twist: the Angular Router itself reports a guard Observable that completes empty as a NavigationError with EmptyError, so take(1) does not avoid the error there; the fix is to make the source emit.
  • Mention firstValueFrom() (rejects with EmptyError, accepts { defaultValue }) for async/await code, and that neither operator helps if the source never emits or completes (the stream just hangs).

Clarifying questions expected:

  • "Can this source legitimately complete without a value (an empty cache, a filtered selector)?"
  • "Is this used in a guard, resolver or effect where an error would block navigation or kill the stream?"

Code / implementation expected: Yes β€” both operators on an empty and a non-empty source, showing the EmptyError.

  • rxjs
  • operators

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

10 min read Β· 11 sections

Read the code

AngularCache hit vs empty cache: take(1), first(), first() with a default, and firstValueFrom
import { Component, signal } from "@angular/core";
import { EMPTY, Observable, firstValueFrom, first, of, take } from "rxjs";

// A cache lookup: emits the cached value, or completes with NO value when the
// cache is empty (like a store selector that filters out undefined, or EMPTY).
function fromCache(hit: boolean): Observable<string> {
  return hit ? of("cached-token", "newer-token") : EMPTY;
}

@Component({
  selector: "app-root",
  template: `
    <button (click)="run(true)">Cache hit</button>
    <button (click)="run(false)">Cache empty</button>
    <button (click)="runPromise()">firstValueFrom(empty)</button>
    <p>take(1): {{ takeResult() }}</p>
    <p>first(): {{ firstResult() }}</p>
    <p>first(undefined, "anonymous"): {{ defaultResult() }}</p>
    <p>firstValueFrom: {{ promiseResult() }}</p>
  `,
})
export class AppComponent {
  takeResult = signal("-");
  firstResult = signal("-");
  defaultResult = signal("-");
  promiseResult = signal("-");

  run(hit: boolean) {
    const describe = (target: typeof this.takeResult) => {
      let got = "no value";
      return {
        next: (v: string) => (got = "value " + v),
        complete: () => target.set(got + ", then complete"),
        error: (e: Error) => target.set("ERROR " + e.name + ": " + e.message),
      };
    };
    fromCache(hit).pipe(take(1)).subscribe(describe(this.takeResult));
    fromCache(hit).pipe(first()).subscribe(describe(this.firstResult));
    fromCache(hit).pipe(first(undefined, "anonymous")).subscribe(describe(this.defaultResult));
  }

  runPromise() {
    // firstValueFrom: the Promise version of first(). It rejects on an empty stream.
    firstValueFrom(fromCache(false)).then(
      (v) => this.promiseResult.set("resolved " + v),
      (e: Error) => this.promiseResult.set("rejected with " + e.name),
    );
  }
}

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 62 of 121 in Angular

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Angular questions
Up nextMedium

How do you bootstrap a standalone Angular application?

Continue