Eager vs Lazy in RxJS

January 21, 2020

I made a mistake many times in RxJS when I use a function to perform a side effect in a stream.

The following factory function creates a Book object.

function createBook({ title, description }): Observable<Book> {
  return of({ title, description });
}

I want to use this function in a pipeline, let’s imagine a simple HTTP application.

fromRoute('/book', 'POST').pipe(
  switchMap(({ title, description }) => createBook({ title, description })),
  map((book) => ({ status: 204, body: book }))
);

Here the book is created only when /book endpoint is hit by a user. That’s because we’re just returning an observable with no side effects.

Now, imagine the following scenario where we need to perform a side effect operation using a database. Let’s assume that DB.create returns a Promise.

class BookDAO {
  static create({ title, description }): Observable<Book> {
    return from(
      DB.create({ title, description }) // 👈 Book created eagerly
    );
  }
}

fromRoute('/book', 'POST').pipe(
  switchMap(({ title, description }) =>
    BookDAO.create({ title, description }) // ❌ Book created at route evaluation
  ),
  map(book => ({ status: 204, body: book }))
);

Here the book is created in the database as soon as we bootstrap the server, before the source emits any values. That’s because the DB.create method returns a Promise which is eagerly executed when using the from factory.

To fix this issue we need to defer the operation, for creating the observable only when the source is subscribed.

class BookDAO {
  static create({ title, description }): Observable<Book> {
    return defer(() => DB.create({ title, description })); // 👈 Book creation is deferred
  }
}

fromRoute('/book', 'POST').pipe(
  switchMap(({ title, description }) =>
    BookDAO.create({ title, description }) // ✔️ Book created lazily
  ),
  map(book => ({ status: 204, body: book }))
);

Now the create method is lazy, which means that the resource is created only when the /book endpoint is hit by a user.

I made this mistake many times, especially when a function is used to produce a stream that perform a side effect.

Blog ideas

Upvote what you'd like me to write about next.

  • Nx DTE vs CI job matrix
    Nx Distributed Task Execution (DTE) vs a traditional CI job matrix with manual sharding: a comparison of developer experience, performance, and reliability.
  • Are you ready for a monorepo?
    An opinionated checklist to help you decide whether a monorepo is the right choice for your organization, and how to prepare for the transition.
  • Why monorepos unlock AI coding agents
    Shared context from frontend to backend, consistent conventions, and reliable task graphs: what makes a monorepo the ideal substrate for AI-assisted development.
  • Managing CI flakiness at scale
    Detecting, quarantining, and fixing flaky tests without slowing the pipeline: patterns that survive past 25 engineers.
  • Monitoring CI health
    Tools and techniques for tracking CI performance and reliability over time, and alerting on regressions before they impact developers.
Edouard Bozon

I'm Edouard Bozon, a passionate software engineer based in France, specializing in monorepo architectures, platform engineering, DevOps, and infrastructure. I build tools and workflows that help engineering teams scale their codebases and ship software with confidence.