Interview answer drill

Use this Angular interview question to rehearse a quick answer, common mistake, follow-up, and production pitfall.

Explain Hierarchical Dependency Injection in Angular With a Real Bug ExampleFrontend interview answer

HighIntermediateAngular
Interview focus

This Angular interview question tests whether you can explain Angular hierarchical DI in production: duplicate providers, shadowed singletons, and cache bugs, connect it to production trade-offs, and handle common follow-up questions.

  • Angular hierarchical DI in production: duplicate providers, shadowed singletons, and cache bugs explanation without falling back to memorized docs wording
  • Dependency Injection and Hierarchy reasoning, edge cases, and production failure modes
  • How you would answer the most likely Angular interview follow-up
Practice more Angular interview questions
Interview quick answer

Angular DI is hierarchical, so provider location changes instance count and service lifetime. The useful angle is the real production bug: re-providing a service in a component or standalone route and silently splitting shared state, caches, or coordination logic.

Full interview answer

Bug-first mental model

Angular DI is not a single global container. It is a tree of injectors, and the nearest provider wins. That becomes a production bug when a root service is accidentally re-provided in a component or route and your “singleton” cache, auth flag, or shared state quietly splits into multiple instances.

The real-world bug

“Why does my shared state reset when I navigate?” or “Why does my cache disappear in some components but not others?” The usual answer: you accidentally created multiple instances of the same service.

Where you provide the service

How many instances exist

Typical use

providedIn: 'root'

1 for the whole app

Global singleton (auth, config, global cache)

In a lazy-loaded route or module

1 per lazy-loaded subtree

Feature-scoped state

In a component’s providers

1 per component instance

Local, isolated state

Where you provide a service determines its lifetime and scope

The bug in code

You think you have one cache service, but you accidentally created many.

TYPESCRIPT
@Injectable({ providedIn: 'root' })
export class UserCacheService {
  users: string[] = [];
}

@Component({
  selector: 'app-user-list',
  template: `...`,
  // ❌ BUG: this creates a NEW instance just for this component
  providers: [UserCacheService]
})
export class UserListComponent {
  constructor(public cache: UserCacheService) {}
}
                  

What goes wrong

Other parts of the app inject the root instance of UserCacheService, but UserListComponent gets its own private copy. Now state, caches, and flags are silently out of sync.

How Angular resolves the dependency

1) Check the component’s injector
2) If not found, check parent component injectors
3) Then module/route injectors
4) Finally root injector

The first match wins.

Injection site

Which instance you get

Why

Component with its own providers

Component-scoped instance

Local provider shadows parent/root

Child of that component

Same component-scoped instance

Injector tree inheritance

Some other component elsewhere

Root instance

Different injector branch

How the injector tree affects instance sharing

Another common real bug: lazy-loaded modules

If you provide a service in a lazy-loaded route/module, you get a separate instance from the rest of the app. This often breaks auth state, caches, or WebSocket connections.

TYPESCRIPT
export const routes: Routes = [
  {
    path: 'admin',
    loadComponent: () => import('./admin.component'),
    providers: [AdminStateService] // feature-scoped instance
  }
];
                  

Standalone route providers are their own injector boundary

In modern Angular, route-level providers create an EnvironmentInjector for that branch. That is why a service can be a singleton at the app root but still get a separate instance under one lazy route tree.

TYPESCRIPT
@Injectable({ providedIn: 'root' })
export class FeatureFlagsService {
  enabled = false;
}

export const routes: Routes = [
  {
    path: 'reports',
    providers: [FeatureFlagsService],
    loadComponent: () => import('./reports.component').then(m => m.ReportsComponent)
  }
];

// Outside /reports => root FeatureFlagsService
// Inside /reports => route-scoped FeatureFlagsService
                  

When component-level providers are actually correct

• Reusable UI widgets with isolated state
• Temporary, per-instance state machines
• Editable rows, dialogs, or dynamic components that must not share state

Goal

Where to provide

Why

Global shared state (auth, config, cache)

providedIn: 'root'

Single instance for entire app

Feature-isolated state

Route/module providers

Scoped lifetime

Per-component isolated state

Component providers

New instance per component

Correct scoping strategy

Senior-level pitfalls

• Accidentally re-providing a root service in a component
• Breaking caches or auth state via lazy-loaded module or route providers
• Forgetting that standalone route providers create a new injector boundary
• Assuming “service = singleton” without checking injector scope
• Debugging “state randomly resets” for hours

Interview summary

Angular DI is hierarchical: services are resolved by walking up the injector tree. Where you provide a service determines how many instances exist. Many real-world bugs come from accidentally creating multiple instances by providing a service in a component or lazy-loaded route when you expected a singleton. In standalone Angular, route providers are especially important because they create an EnvironmentInjector for that branch.

Similar questions
Guides
Preparing for interviews?

Use this as one explanation rep, then continue with the Angular interview questions cluster or a guided prep path.