Circular Dependencies: forwardRef() and Module Dependency Cycles
- Last updated
- Prerequisites:
- NestJS Dependency Injection Container
- JavaScript ES Module loading order
- nestjs
- circular-dependency
- forwardRef
- dependency-injection
- architecture
Read at your depth
The practical view
Circular dependencies occur when Service A injects Service B, and Service B injects Service A (or Module A imports Module B, which imports Module A). At runtime, one service receives `undefined` because JavaScript cannot resolve the circular import. NestJS solves this using `forwardRef(() => TargetClass)` on both the module import and the constructor `@Inject(forwardRef(() => TargetService))`.
The same idea in other frameworks
`@Inject(forwardRef(() => UsersService)) private usersService: UsersService`.Spring `@Lazy` annotation on constructor parameters to break circular dependency graphs.Angular `forwardRef(() => CustomComponent)` in control value accessors.FastAPI string annotation forward references or function-level dependency injection.Legacy vs modern
Circular Import Crash vs Resolved forwardRef() Injection
Circular injection causes `TypeError: Cannot read properties of undefined`; `forwardRef()` defers reference resolution.
constructor(private usersService: UsersService) {} // ❌ Receives undefined at runtimeconstructor(@Inject(forwardRef(() => UsersService)) private usersService: UsersService) {} // ✅ NEWInterview gotchas
Context
Interviewers test your practical debugging experience with circular dependency resolution.
The mechanical answer
You must use `forwardRef()` on **both the Module imports and the Service constructors**. In `UsersModule`, use `imports: [forwardRef(() => OrdersModule)]`. In `OrdersModule`, use `imports: [forwardRef(() => UsersModule)]`. Additionally, in the service constructor parameters, you must add `@Inject(forwardRef(() => ServiceName))` on both sides.
Trap
Adding `forwardRef` only to the services while forgetting the module `imports` array will still cause module compilation crashes.
Context
Checks domain-driven design and refactoring skills.
The mechanical answer
The architectural best practice is to refactor the shared domain logic using **Dependency Inversion** or by introducing a third intermediary module/service (e.g. `CommonModule` or an `EventBus`). Instead of Service A and Service B calling each other directly, they emit events or both depend on an extracted shared Service C.
Trap
Treat `forwardRef()` as an emergency escape hatch, not a permanent architectural design pattern.