# Dependency Injection (Part 2: Scopes, Cycles, and Custom Tokens)

Part 1 covered the mechanics of constructor injection and why a container beats hand-wiring. This part covers three things that only come up once an application grows past the trivial case: how long an instance lives, what happens when two classes need each other, and how to inject something that isn't a class at all.

## 1. Provider Scope: Singleton by Default

Unless told otherwise, a DI container constructs each provider **once** and hands out the same instance to every consumer for the lifetime of the application — this is singleton scope, and it's the default precisely because most services (a repository, a mailer, a config reader) are stateless and safe to share:

```ts
@Injectable()
class OrderService { /* constructed once, shared everywhere */ }
```

Two different controllers that both depend on `OrderService` receive the **same object reference** — not two separate instances. This matters if a provider accidentally holds mutable per-request state, since that state would then leak across unrelated requests handled by different controllers sharing the same singleton instance.

## 2. Request Scope: One Instance Per Request

When a provider genuinely needs to hold state specific to a single incoming request (the authenticated user, a request-id for logging correlation), request scope creates a fresh instance for every request instead:

```ts
import { Injectable, Scope } from "@nestjs/common";

@Injectable({ scope: Scope.REQUEST })
class RequestContextService {
  userId: number | null = null; // safe to mutate — this instance is never shared across requests
}
```

Request-scoped providers cost more (a new instance, and a new instance of everything that transitively depends on it, per request) so they're reserved for genuinely per-request state — reaching for `Scope.REQUEST` by default "just to be safe" defeats the performance benefit of the shared-singleton default for the vast majority of stateless services.

![Singleton providers are shared by all requests, request-scoped providers are rebuilt per request and make their dependents request-scoped, transient providers are new per injection site](diagrams/01-provider-scopes.svg)

## 3. Transient Scope: A Fresh Instance Per Injection Site

A third, rarer scope creates a brand-new instance every time the provider is injected anywhere, even multiple times within the same request:

```ts
@Injectable({ scope: Scope.TRANSIENT })
class UniqueIdGenerator { /* a new instance per injection site, not shared at all */ }
```

This is uncommon in practice — most things needing "freshness" are better modeled as a plain method call (`generator.next()`) on a shared singleton rather than a new *instance* of the whole provider — but it exists for genuinely stateful, non-shareable helper objects.

## 4. Circular Dependencies

Two providers can end up needing each other — `UserService` needs `OrderService` to look up a user's orders, and `OrderService` needs `UserService` to validate the order's owner exists:

```ts
@Injectable()
class UserService {
  constructor(private orderService: OrderService) {}
}

@Injectable()
class OrderService {
  constructor(private userService: UserService) {} // circular!
}
```

Resolved naively, this is unconstructable: building `UserService` requires an `OrderService`, which requires a `UserService`, forever. Frameworks like NestJS provide an explicit escape hatch — a forward-reference wrapper that defers resolution of one side until both classes exist:

```ts
import { forwardRef, Inject } from "@nestjs/common";

@Injectable()
class UserService {
  constructor(@Inject(forwardRef(() => OrderService)) private orderService: OrderService) {}
}
```

`forwardRef` is a workaround, not a fix — a genuine circular dependency between two services is usually a sign that a piece of shared logic belongs in a third, lower-level service both can depend on *without* depending on each other, which is worth reaching for before reaching for `forwardRef` as the default fix.

## 5. Custom Tokens: Injecting Non-Class Dependencies

Constructor injection as shown so far relies on a class's own type as the lookup key — but not every dependency is a class. A raw config value, a third-party client instance, or an interface (which, unlike a class, doesn't exist at runtime in TypeScript — see `languages/typescript/01-type_system_fundamentals` on type erasure) all need a different mechanism: an explicit string or symbol **token**.

```ts
import { Module, Inject } from "@nestjs/common";

const DATABASE_CONNECTION = "DATABASE_CONNECTION"; // a token — just a unique key

@Module({
  providers: [
    {
      provide: DATABASE_CONNECTION,
      useFactory: () => createConnection(process.env.DATABASE_URL!), // arbitrary construction logic
    },
  ],
})
class DatabaseModule {}

@Injectable()
class OrderRepository {
  constructor(@Inject(DATABASE_CONNECTION) private db: Connection) {} // injected by token, not by class
}
```

This is also the mechanism that lets an interface be "injected" in spirit: define a token for `IOrderRepository`, provide a concrete `PostgresOrderRepository` against that token in production and an `InMemoryOrderRepository` against the same token in tests — consuming code depends only on the token and the interface shape, never on which concrete class actually satisfies it, which is the request-time realization of the dependency-inversion principle from part 1.

## Common Pitfalls

1. **Assuming every provider is automatically request-scoped.** The default is singleton — a provider holding mutable state without an explicit `Scope.REQUEST` will leak that state across unrelated requests.
2. **Reaching for `Scope.REQUEST` everywhere "to be safe."** Most services are genuinely stateless; blanket request-scoping adds real construction overhead per request for no benefit on services that don't need it.
3. **Treating `forwardRef` as a normal pattern rather than a smell.** A circular dependency usually means shared logic should be extracted into a third, lower-level provider both sides depend on.
4. **Trying to inject an interface directly by type.** TypeScript interfaces are erased at compile time and don't exist as values at runtime — a container needs a class or an explicit token to resolve against, never a bare interface.

## Quick Reference

```
Singleton (default) -> one instance, shared by every consumer, for the app's lifetime
Scope.REQUEST        -> one fresh instance per incoming request — for genuinely per-request state
Scope.TRANSIENT       -> a fresh instance per injection site — rare, usually a method call suffices instead
Circular dependency   -> forwardRef(() => X) defers resolution; usually a sign to extract shared logic
Custom token          -> a string/symbol key for injecting non-class values (config, clients, interfaces)
useFactory             -> arbitrary construction logic behind a token, run once by the container
```
