Circular Dependencies

⭐ Interview Importance: LOW
⏱️ Revision Time: 7 min

A circular dependency occurs when two classes depend on each other. For example, class A needs class B, and class B also needs class A.

Overview

Circular dependencies can arise in NestJS between Modules (Module A imports Module B, and Module B imports Module A) and between Providers/Services (Service A injects Service B, and Service B injects Service A).

When this happens, the NestJS IoC container cannot resolve the dependency graph because it gets stuck in an infinite loop trying to instantiate them. It will throw a fatal error during application bootstrap.

While the best solution is to refactor your code to avoid the circular dependency entirely, NestJS provides the forwardRef() utility function to resolve them when they are unavoidable.

Key Concepts

  • forwardRef(): A utility function that allows Nest to reference classes which aren’t yet defined.
  • @Inject(forwardRef(() => Class)): How you apply the forward reference in a constructor.
  • Module Circularity: Forward references must be applied on both sides of the circular dependency.

Code Examples

Resolving Service Circularity

Let’s say CatsService and CommonService depend on each other.

// cats.service.ts
import { Injectable, Inject, forwardRef } from '@nestjs/common';
import { CommonService } from './common.service';

@Injectable()
export class CatsService {
  constructor(
    // Wrap the token in forwardRef()
    @Inject(forwardRef(() => CommonService))
    private commonService: CommonService,
  ) {}
}
// common.service.ts
import { Injectable, Inject, forwardRef } from '@nestjs/common';
import { CatsService } from './cats.service';

@Injectable()
export class CommonService {
  constructor(
    // Must also wrap the token on the other side!
    @Inject(forwardRef(() => CatsService))
    private catsService: CatsService,
  ) {}
}

Resolving Module Circularity

The same concept applies if CatsModule and CommonModule import each other.

// cats.module.ts
import { Module, forwardRef } from '@nestjs/common';
import { CommonModule } from './common.module';

@Module({
  // Use forwardRef in the imports array
  imports: [forwardRef(() => CommonModule)],
})
export class CatsModule {}
// common.module.ts
import { Module, forwardRef } from '@nestjs/common';
import { CatsModule } from './cats.module';

@Module({
  // Must be used on both sides
  imports: [forwardRef(() => CatsModule)],
})
export class CommonModule {}

Best Practices

  • Refactor First: A circular dependency is almost always a sign of a design flaw. Before using forwardRef(), ask yourself: “Can I extract the shared logic into a third service/module that both of these depend on?” (e.g., extracting to a SharedUtilityService).
  • ModuleRef: If you have deep circular dependencies that forwardRef() struggles with, you can use the ModuleRef class to manually retrieve the dependency later in the lifecycle (e.g., inside an onModuleInit hook) rather than in the constructor.