Lesson 44 of 5020 min read

NestJS Dependency Injection Scopes: Singleton, Request and Transient

Learn the three NestJS provider scopes, Singleton, Request, and Transient, including when each is appropriate and the performance trade-offs involved.

Author: CodersNexus

NestJS Dependency Injection Scopes: Singleton, Request and Transient

Back in Module 1, you learned that NestJS providers default to singleton scope, meaning one shared instance serves the entire application. That's true for the vast majority of services you'll ever write, but NestJS actually supports two additional scopes, and understanding when (rarely) you need them, and the real performance cost of using them unnecessarily, is valuable, interview-relevant knowledge.

NestJS Dependency Injection Scopes: Learning Objectives

  • Recall why singleton scope is NestJS's default and why it works for most providers.
  • Understand request scope and when a provider genuinely needs per-request state.
  • Understand transient scope and how it differs from request scope.
  • Configure a provider's scope explicitly using the @Injectable() decorator's options.
  • Recognize the performance trade-offs of request and transient scope versus the default.

NestJS Dependency Injection Scopes: Key Terms and Definitions

  • Singleton scope: The default NestJS provider scope, where exactly one instance is created and shared across the entire application's lifetime.
  • Request scope: A provider scope where a new instance is created for every single incoming request, then discarded once that request completes.
  • Transient scope: A provider scope where a new instance is created every single time the provider is injected, even multiple times within the same request.
  • Scope bubbling: The phenomenon where a provider depending on a request-scoped or transient provider also effectively becomes request-scoped or transient itself.
  • Scope: { scope: Scope.REQUEST }: The configuration option passed to @Injectable() to change a provider's default scope.

How NestJS Dependency Injection Scopes Works: Detailed Explanation

By default, every NestJS provider is singleton-scoped: NestJS creates exactly one instance when the application starts, and every class that injects it receives that same shared instance for the entire lifetime of the application. This is efficient and correct for the overwhelming majority of services, which are typically stateless, meaning they don't store any request-specific data as instance properties, only performing operations based on whatever arguments are passed into their methods.

Request scope exists for the rare cases where a provider genuinely needs to hold state specific to a single incoming request, state that would be incorrect or even a security risk to share across different requests. A common example is a service that needs direct access to request-specific context, like a multi-tenant application's current tenant ID, stored as an instance property set once per request rather than passed as a parameter to every single method call. Configuring a provider as request-scoped, using @Injectable({ scope: Scope.REQUEST }), tells NestJS to create a brand-new instance of that provider for every incoming request, discarding it once the request completes, guaranteeing no state ever leaks between different users' concurrent requests.

Transient scope goes a step further: rather than one instance per request, a new instance is created every single time the provider is injected into anything, even multiple times within processing the same request. This is useful in narrower scenarios, such as a logging or utility service where each consumer needs its own independent instance, perhaps configured slightly differently, without any shared or request-level state at all.

The critical trade-off to understand deeply, and a common interview question, is performance and what's called scope bubbling. Creating a new instance for every request (or every injection) is inherently more expensive than reusing one shared singleton instance, since NestJS must re-run that provider's entire construction and dependency resolution process repeatedly rather than once. Additionally, if a singleton-scoped provider (like a widely-used core service) ends up depending on a request-scoped or transient provider somewhere in its dependency chain, that singleton provider itself effectively becomes request-scoped too, since NestJS cannot safely share a single instance of something that itself depends on per-request state. This bubbling effect can inadvertently make large portions of an application request-scoped, and therefore meaningfully slower, if request or transient scope is introduced carelessly deep in a commonly-used dependency chain.

Interview-Friendly Explanation

A strong interview or viva answer for nestjs dependency injection scopes must go beyond a one-line definition. Interviewers evaluating experienced backend developers reward answers that combine a precise definition, a clear working explanation, a real-world example, and a conclusion that highlights why it matters in production Node.js applications. Use the four-point framework below when answering under time pressure.

  • Definition point: Singleton scope: The default NestJS provider scope, where exactly one instance is created and shared across the entire application's lifetime.
  • Working point: Understand request scope and when a provider genuinely needs per-request state.
  • Example point: Multi-tenant SaaS platforms are the most common real-world use case for request-scoped providers, exactly like TenantContextService, ensuring tenant identification never accidentally leaks between different customers' concurrent requests.
  • Conclusion point: Singleton scope should remain your default assumption; only reach for request or transient scope with a specific, genuine reason.

How to Answer This in a Technical Interview

  1. Give a two-to-three sentence definition using correct NestJS and Node.js terminology.
  2. Add one specific example drawn from a real backend scenario such as an e-commerce, fintech, or SaaS API.
  3. Mention a relevant trade-off or alternative approach, since interviewers often probe this contrast.
  4. Close with one benefit, limitation, or production use case.
  5. Avoid vague answers that only restate the term — interviewers filter these out immediately.

Practical Scenario

Imagine you are scaling a production backend for a SaaS product, similar to systems used at companies like Razorpay, Freshworks, or Zomato. Every lesson in this module maps directly to a decision you will make while making that backend more advanced, performant, and maintainable at scale.

NestJS Dependency Injection Scopes: Architecture and Flow Diagram

Visualize the three scopes and their instance creation frequency:

[Singleton (default)] --> One instance created at startup --> Shared by every consumer, forever
[Request] --> New instance created per incoming HTTP request --> Shared within that one request, then discarded
[Transient] --> New instance created every single time it's injected --> Never shared, even within the same request

Scope bubbling: [SingletonService depends on RequestScopedService] --> SingletonService effectively becomes request-scoped too

NestJS Dependency Injection Scopes: Scope Reference Table

ScopeInstance LifetimePerformance CostTypical Use Case
Singleton (default)Entire application lifetimeLowest — created onceThe vast majority of stateless services
RequestOne incoming HTTP requestHigher — recreated per requestGenuinely needing per-request state (e.g. tenant context)
TransientEvery single injectionHighest — recreated per injectionIndependent, unshared utility instances

NestJS Dependency Injection Scopes: NestJS Code Example

import { Injectable, Scope, Inject } from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { Request } from 'express';

// Request-scoped: a new instance per incoming request, with access to that request
@Injectable({ scope: Scope.REQUEST })
export class TenantContextService {
  private tenantId: string;

  constructor(@Inject(REQUEST) private readonly request: Request) {
    // Runs fresh for every request; safe to read request-specific headers here
    this.tenantId = (request.headers['x-tenant-id'] as string) || 'default';
  }

  getTenantId(): string {
    return this.tenantId;
  }
}

// Transient: a brand-new instance every time it's injected, anywhere
@Injectable({ scope: Scope.TRANSIENT })
export class RequestIdGenerator {
  private readonly id = Math.random().toString(36).slice(2, 10);

  getId(): string {
    return this.id; // each injected instance has its own independent, unique id
  }
}

// A default, singleton-scoped service (the overwhelming majority case)
@Injectable() // no scope specified = Scope.DEFAULT (singleton)
export class ProductsService {
  private cache = new Map(); // safe to share across every request, since it's not user-specific

  findAll() {
    return Array.from(this.cache.values());
  }
}

TenantContextService is explicitly marked request-scoped, allowing it to safely inject Express's raw Request object using NestJS's special REQUEST token, and read a request-specific header to determine the current tenant, state that would be dangerously incorrect to share across different users' concurrent requests if this were singleton-scoped instead. RequestIdGenerator demonstrates transient scope: every single place that injects it receives a completely separate instance with its own independently generated id, useful when even sharing within the same request would be inappropriate. ProductsService, with no scope specified at all, defaults to singleton, which is entirely appropriate here since its in-memory cache is meant to be shared globally across all requests, not scoped to any individual one.

Real-World NestJS Dependency Injection Scopes Industry Examples

  • Multi-tenant SaaS platforms are the most common real-world use case for request-scoped providers, exactly like TenantContextService, ensuring tenant identification never accidentally leaks between different customers' concurrent requests.
  • Request-tracing and correlation ID systems sometimes use request-scoped providers to attach a unique trace ID to every log statement generated during the processing of a single request, aiding debugging in distributed systems.
  • The vast majority of production NestJS services, database repositories, business logic services, utility helpers, remain singleton-scoped by default, since introducing unnecessary request or transient scope would degrade performance without any real benefit.
  • Performance-conscious teams specifically audit their dependency graphs to catch accidental scope bubbling, where a deeply shared, otherwise-singleton service unintentionally becomes request-scoped due to one small, careless dependency.

NestJS Dependency Injection Scopes Interview Questions and Answers

Q1. What is the default provider scope in NestJS, and why is it appropriate for most services?

Short answer: The default scope is singleton, meaning exactly one instance of a provider is created for the entire application's lifetime and shared by every consumer. This is appropriate for most services because they are typically stateless, performing operations based on method arguments rather than storing request-specific data as instance properties.

Detailed explanation: By default, every NestJS provider is singleton-scoped: NestJS creates exactly one instance when the application starts, and every class that injects it receives that same shared instance for the entire lifetime of the application. This is efficient and correct for the overwhelming majority of services, which are typically stateless, meaning they don't store any request-specific data as instance properties, only performing operations based on whatever arguments are passed into their methods. Request scope exists for the rare cases where a provider genuinely needs to hold state specific to a single incoming request, state that would be incorrect or even a security risk to share across different requests. A common example is a service that needs direct access to request-specific context, like a multi-tenant application's current tenant ID, stored as an instance property set once per request rather than passed as a parameter to every single method call. Configuring a provider as request-scoped, using @Injectable({ scope: Scope.REQUEST }), tells NestJS to create a brand-new instance of that provider for every incoming request, discarding it once the request completes, guaranteeing no state ever leaks between different users' concurrent requests. Transient scope goes a step further: rather than one instance per request, a new instance is created every single time the provider is injected into anything, even multiple times within processing the same request. This is useful in narrower scenarios, such as a logging or utility service where each consumer needs its own independent instance, perhaps configured slightly differently, without any shared or request-level state at all. The critical trade-off to understand deeply, and a common interview question, is performance and what's called scope bubbling. Creating a new instance for every request (or every injection) is inherently more expensive than reusing one shared singleton instance, since NestJS must re-run that provider's entire construction and dependency resolution process repeatedly rather than once. Additionally, if a singleton-scoped provider (like a widely-used core service) ends up depending on a request-scoped or transient provider somewhere in its dependency chain, that singleton provider itself effectively becomes request-scoped too, since NestJS cannot safely share a single instance of something that itself depends on per-request state. This bubbling effect can inadvertently make large portions of an application request-scoped, and therefore meaningfully slower, if request or transient scope is introduced carelessly deep in a commonly-used dependency chain.

Practical example: Multi-tenant SaaS platforms are the most common real-world use case for request-scoped providers, exactly like TenantContextService, ensuring tenant identification never accidentally leaks between different customers' concurrent requests.

Interview tip: Singleton (default): one instance, entire app lifetime — appropriate for the vast majority of stateless services.

Revision hook: Singleton scope should remain your default assumption; only reach for request or transient scope with a specific, genuine reason.

Q2. When would you use a request-scoped provider instead of the default singleton scope?

Short answer: Request scope is appropriate when a provider genuinely needs to hold state specific to a single incoming request, such as a multi-tenant application's current tenant ID, where sharing that state across different concurrent requests via a singleton instance would be incorrect or a security risk.

Detailed explanation: TenantContextService is explicitly marked request-scoped, allowing it to safely inject Express's raw Request object using NestJS's special REQUEST token, and read a request-specific header to determine the current tenant, state that would be dangerously incorrect to share across different users' concurrent requests if this were singleton-scoped instead. RequestIdGenerator demonstrates transient scope: every single place that injects it receives a completely separate instance with its own independently generated id, useful when even sharing within the same request would be inappropriate. ProductsService, with no scope specified at all, defaults to singleton, which is entirely appropriate here since its in-memory cache is meant to be shared globally across all requests, not scoped to any individual one.

Practical example: Request-tracing and correlation ID systems sometimes use request-scoped providers to attach a unique trace ID to every log statement generated during the processing of a single request, aiding debugging in distributed systems.

Interview tip: Request: one instance per incoming request — needed only when genuinely holding per-request state.

Revision hook: Multi-tenancy and per-request context are the most common legitimate reasons to use request scope in real applications.

Q3. What is scope bubbling, and why is it an important performance consideration?

Short answer: Scope bubbling occurs when a singleton-scoped provider depends, directly or indirectly, on a request-scoped or transient provider, causing that singleton provider to effectively become request-scoped itself, since NestJS cannot safely share one instance of something depending on per-request state. This can unintentionally make large portions of an application request-scoped and therefore slower if introduced carelessly.

Detailed explanation: NestJS provides three provider scopes: singleton, the default, where one instance is shared across the entire application's lifetime and is appropriate for the vast majority of stateless services; request scope, where a new instance is created for each incoming HTTP request, useful when a provider genuinely needs to hold per-request state like a multi-tenant application's current tenant ID; and transient scope, where an entirely new instance is created for every single injection point, even within the same request. Configuring scope explicitly through @Injectable({ scope: Scope.REQUEST }) or Scope.TRANSIENT comes with a real performance cost compared to the default singleton, compounded by scope bubbling, where a singleton depending on a request-scoped or transient provider effectively inherits that broader scope too. Understanding these trade-offs ensures scope is used deliberately, only where genuinely needed, rather than by default.

Practical example: The vast majority of production NestJS services, database repositories, business logic services, utility helpers, remain singleton-scoped by default, since introducing unnecessary request or transient scope would degrade performance without any real benefit.

Interview tip: Transient: one instance per injection point, even within the same request — narrowest, most specialized use case.

Revision hook: Scope bubbling is a subtle but real performance trap worth actively auditing for in larger codebases.

Q4. What is the difference between request scope and transient scope?

Short answer: Request scope creates one new instance per incoming HTTP request, shared by every consumer within that same request. Transient scope goes further, creating a completely new instance every single time the provider is injected, even multiple times within the processing of the same request, meaning instances are never shared at all.

Detailed explanation: By default, every NestJS provider is singleton-scoped: NestJS creates exactly one instance when the application starts, and every class that injects it receives that same shared instance for the entire lifetime of the application. This is efficient and correct for the overwhelming majority of services, which are typically stateless, meaning they don't store any request-specific data as instance properties, only performing operations based on whatever arguments are passed into their methods. Request scope exists for the rare cases where a provider genuinely needs to hold state specific to a single incoming request, state that would be incorrect or even a security risk to share across different requests. A common example is a service that needs direct access to request-specific context, like a multi-tenant application's current tenant ID, stored as an instance property set once per request rather than passed as a parameter to every single method call. Configuring a provider as request-scoped, using @Injectable({ scope: Scope.REQUEST }), tells NestJS to create a brand-new instance of that provider for every incoming request, discarding it once the request completes, guaranteeing no state ever leaks between different users' concurrent requests. Transient scope goes a step further: rather than one instance per request, a new instance is created every single time the provider is injected into anything, even multiple times within processing the same request. This is useful in narrower scenarios, such as a logging or utility service where each consumer needs its own independent instance, perhaps configured slightly differently, without any shared or request-level state at all. The critical trade-off to understand deeply, and a common interview question, is performance and what's called scope bubbling. Creating a new instance for every request (or every injection) is inherently more expensive than reusing one shared singleton instance, since NestJS must re-run that provider's entire construction and dependency resolution process repeatedly rather than once. Additionally, if a singleton-scoped provider (like a widely-used core service) ends up depending on a request-scoped or transient provider somewhere in its dependency chain, that singleton provider itself effectively becomes request-scoped too, since NestJS cannot safely share a single instance of something that itself depends on per-request state. This bubbling effect can inadvertently make large portions of an application request-scoped, and therefore meaningfully slower, if request or transient scope is introduced carelessly deep in a commonly-used dependency chain.

Practical example: Performance-conscious teams specifically audit their dependency graphs to catch accidental scope bubbling, where a deeply shared, otherwise-singleton service unintentionally becomes request-scoped due to one small, careless dependency.

Interview tip: Scope bubbling: a singleton depending on a request/transient provider effectively inherits that broader scope too.

Revision hook: Understanding these trade-offs, not just the syntax, is what separates a surface-level answer from a strong one in interviews.

NestJS Dependency Injection Scopes MCQs and Practice Questions

1. What is the default scope for a NestJS provider if none is explicitly specified?

  1. Scope.REQUEST
  2. Scope.TRANSIENT
  3. Scope.DEFAULT (singleton)
  4. There is no default; scope must always be specified

Answer: C. Scope.DEFAULT (singleton)

Explanation: Unless explicitly configured otherwise, every NestJS provider defaults to singleton scope, meaning one shared instance serves the entire application.

Concept link: Singleton (default): one instance, entire app lifetime — appropriate for the vast majority of stateless services.

Why this matters: Singleton scope should remain your default assumption; only reach for request or transient scope with a specific, genuine reason.

2. Which scope creates a new provider instance for every single incoming HTTP request?

  1. Singleton
  2. Request
  3. Transient
  4. Global

Answer: B. Request

Explanation: Request scope creates a fresh instance of the provider for each incoming request, which is then shared by any consumers within that same request before being discarded.

Concept link: Request: one instance per incoming request — needed only when genuinely holding per-request state.

Why this matters: Multi-tenancy and per-request context are the most common legitimate reasons to use request scope in real applications.

3. What happens if a singleton-scoped service depends on a request-scoped service?

  1. Nothing changes; it remains singleton
  2. The singleton-scoped service effectively also becomes request-scoped, a phenomenon called scope bubbling
  3. NestJS throws a compile-time error
  4. The request-scoped dependency is silently ignored

Answer: B. The singleton-scoped service effectively also becomes request-scoped, a phenomenon called scope bubbling

Explanation: Since a singleton cannot safely hold onto request-specific state shared across all requests, depending on a request-scoped provider causes the dependent provider to effectively bubble up to request scope as well.

Concept link: Transient: one instance per injection point, even within the same request — narrowest, most specialized use case.

Why this matters: Scope bubbling is a subtle but real performance trap worth actively auditing for in larger codebases.

4. Which scope creates a brand-new instance every single time a provider is injected, even multiple times in one request?

  1. Singleton
  2. Request
  3. Transient
  4. Static

Answer: C. Transient

Explanation: Transient scope goes further than request scope, ensuring a completely new, independent instance is created for every individual injection point, never shared even within the same request.

Concept link: Scope bubbling: a singleton depending on a request/transient provider effectively inherits that broader scope too.

Why this matters: Understanding these trade-offs, not just the syntax, is what separates a surface-level answer from a strong one in interviews.

Common NestJS Dependency Injection Scopes Mistakes to Avoid

  • Using request or transient scope by default 'just in case', without a genuine need, unnecessarily degrading application performance.
  • Not realizing that a widely-depended-upon singleton service has become request-scoped due to scope bubbling from one careless dependency deep in its chain.
  • Storing request-specific state as instance properties on a singleton-scoped service, causing data to incorrectly leak or overwrite between different users' concurrent requests.
  • Forgetting that request-scoped providers require re-instantiation and re-injection on every request, which is measurably more expensive than a singleton at high request volumes.

NestJS Dependency Injection Scopes: Interview Notes and Exam Tips

  • Singleton (default): one instance, entire app lifetime — appropriate for the vast majority of stateless services.
  • Request: one instance per incoming request — needed only when genuinely holding per-request state.
  • Transient: one instance per injection point, even within the same request — narrowest, most specialized use case.
  • Scope bubbling: a singleton depending on a request/transient provider effectively inherits that broader scope too.

Key NestJS Dependency Injection Scopes Takeaways

  • Singleton scope should remain your default assumption; only reach for request or transient scope with a specific, genuine reason.
  • Multi-tenancy and per-request context are the most common legitimate reasons to use request scope in real applications.
  • Scope bubbling is a subtle but real performance trap worth actively auditing for in larger codebases.
  • Understanding these trade-offs, not just the syntax, is what separates a surface-level answer from a strong one in interviews.

NestJS Dependency Injection Scopes: Summary

NestJS provides three provider scopes: singleton, the default, where one instance is shared across the entire application's lifetime and is appropriate for the vast majority of stateless services; request scope, where a new instance is created for each incoming HTTP request, useful when a provider genuinely needs to hold per-request state like a multi-tenant application's current tenant ID; and transient scope, where an entirely new instance is created for every single injection point, even within the same request. Configuring scope explicitly through @Injectable({ scope: Scope.REQUEST }) or Scope.TRANSIENT comes with a real performance cost compared to the default singleton, compounded by scope bubbling, where a singleton depending on a request-scoped or transient provider effectively inherits that broader scope too. Understanding these trade-offs ensures scope is used deliberately, only where genuinely needed, rather than by default.

Frequently Asked Questions

No, the overwhelming majority of providers in most applications remain perfectly correct and more performant as the default singleton scope; request and transient scope are specialized tools reserved for specific scenarios like multi-tenancy or independent utility instances, not a general-purpose default. In interviews, tie this back to: Singleton (default): one instance, entire app lifetime — appropriate for the vast majority of stateless services. In real applications, consider this example: Multi-tenant SaaS platforms are the most common real-world use case for request-scoped providers, exactly like TenantContextService, ensuring tenant identification never accidentally leaks between different customers' concurrent requests. Key revision takeaway: Singleton scope should remain your default assumption; only reach for request or transient scope with a specific, genuine reason.

You use NestJS's special REQUEST injection token, imported from @nestjs/core, combined with the @Inject() decorator, such as constructor(@Inject(REQUEST) private readonly request: Request), which is only meaningfully available within request-scoped (or transient) providers. In interviews, tie this back to: Request: one instance per incoming request — needed only when genuinely holding per-request state. In real applications, consider this example: Request-tracing and correlation ID systems sometimes use request-scoped providers to attach a unique trace ID to every log statement generated during the processing of a single request, aiding debugging in distributed systems. Key revision takeaway: Multi-tenancy and per-request context are the most common legitimate reasons to use request scope in real applications.

Yes, controllers can also be configured with a scope using the same @Injectable()-style options (via the ApiTags-like class decorator options on @Controller() or by extending the scope from an injected request-scoped provider), though it's far more common to scope individual services rather than entire controllers. In interviews, tie this back to: Transient: one instance per injection point, even within the same request — narrowest, most specialized use case. In real applications, consider this example: The vast majority of production NestJS services, database repositories, business logic services, utility helpers, remain singleton-scoped by default, since introducing unnecessary request or transient scope would degrade performance without any real benefit. Key revision takeaway: Scope bubbling is a subtle but real performance trap worth actively auditing for in larger codebases.

It introduces additional overhead compared to singleton scope, since instances must be recreated per request, but whether this is a meaningful, noticeable performance concern depends on your application's request volume and how deep or widespread the request-scoped dependency chain becomes due to scope bubbling. In interviews, tie this back to: Scope bubbling: a singleton depending on a request/transient provider effectively inherits that broader scope too. In real applications, consider this example: Performance-conscious teams specifically audit their dependency graphs to catch accidental scope bubbling, where a deeply shared, otherwise-singleton service unintentionally becomes request-scoped due to one small, careless dependency. Key revision takeaway: Understanding these trade-offs, not just the syntax, is what separates a surface-level answer from a strong one in interviews.

One common strategy is minimizing how deep or widely a request-scoped provider is depended upon, isolating request-specific logic into a small, narrowly-used service rather than letting it become a dependency of many broadly-shared singleton services, which would otherwise all inherit the broader scope. In interviews, tie this back to: Singleton (default): one instance, entire app lifetime — appropriate for the vast majority of stateless services. In real applications, consider this example: Multi-tenant SaaS platforms are the most common real-world use case for request-scoped providers, exactly like TenantContextService, ensuring tenant identification never accidentally leaks between different customers' concurrent requests. Key revision takeaway: Singleton scope should remain your default assumption; only reach for request or transient scope with a specific, genuine reason.

Passing data as a method parameter keeps a service singleton and avoids any scope-related performance cost, and is often preferable when practical. Request scope becomes genuinely useful when request-specific context needs to be available implicitly across many different method calls or deeply nested service calls without manually threading it through every single function signature. In interviews, tie this back to: Request: one instance per incoming request — needed only when genuinely holding per-request state. In real applications, consider this example: Request-tracing and correlation ID systems sometimes use request-scoped providers to attach a unique trace ID to every log statement generated during the processing of a single request, aiding debugging in distributed systems. Key revision takeaway: Multi-tenancy and per-request context are the most common legitimate reasons to use request scope in real applications.