Angular – Interview Questions

August 17, 20265 min readUpdated 8/21/2026

The questions Angular interviews actually ask, answered the way you would say them out loud. Every one of these is answerable from the demo application this track is built on, which is the point — the difference between people who have shipped Angular and people who have read about it usually shows up in the follow-up question.

Signals versus observables — when do you use which?

A signal always has a value you can read synchronously. An observable is a sequence of events over time that may emit nothing, or many things, or fail.

So: state is a signal — the cart, the current user, the menu. Events are an observable — a debounced search, a websocket. Convert at the boundary with toSignal and toObservable rather than picking one for the whole application.

Follow-up you should expect: "Give me a case where an observable is clearly right." Search-as-you-type. You need debounceTime to avoid a request per keystroke and switchMap to cancel a stale one — signals model neither, because both are about time.

Why did standalone components replace NgModules?

An NgModule was a second place to declare things, and it did not carry its weight. A component already knows what its template uses; putting that list in a separate file meant every new component needed an edit somewhere else, and "add it to declarations" was the most common first error anyone made.

Standalone components declare their own imports. The dependency graph is then visible in the file you are reading, and the bundler can follow it — which is also what makes tree-shaking and lazy routes straightforward.

Explain OnPush.

By default Angular re-checks every component after anything asynchronous happens. OnPush says: skip this component unless an input reference changed, a signal it read changed, or an event fired inside it.

The follow-up that separates people: "What breaks under OnPush?" Mutation. Push onto an array passed as an input and the reference is unchanged, so the child never re-renders. Replace, do not mutate.

And if they know React: OnPush is React.memo without needing useCallback, because a handler bound with (selected)="…" is not an input and cannot invalidate the child.

What is zoneless mode?

Zone.js monkey-patched every async browser API so Angular knew when to check the whole component tree. Signals know precisely which templates read them, so that global net is no longer needed. Removing it saves about 13 kB, cleans up stack traces, and stops Angular interfering with non-Angular code.

Follow-up: "What breaks?" State that Angular cannot see. A plain field mutated from a setTimeout will not repaint, because nothing told the framework. Put state in signals.

How does dependency injection resolve a dependency?

By token, walking up a hierarchy of injectors: the component's, its parents', then the root. First match wins.

providedIn: 'root' gives one instance at the top — a singleton, which is what makes a service holding signals a shared store. Providing on a route gives every component under it its own instance, destroyed on navigation. Providing on a component gives each instance its own.

Follow-up: "Why inject() rather than a constructor parameter?" It works where there is no constructor — functional guards, interceptors, and plain helper functions — and it composes.

Template-driven or reactive forms?

Reactive for anything non-trivial: the form is an object in the class, so it is typed, testable, patchable from an API, and able to express conditional validation. Template-driven for two or three fields with no cross-field rules, where it is genuinely less code.

Follow-up: "Show me something template-driven cannot do." Make a field required only when another field has a particular value. That needs setValidators plus updateValueAndValidity — and forgetting the second call is a classic bug: the form stays invalid with nothing on screen to explain why.

What does track do in @for, and why is it required?

It tells Angular how to recognise the same item across renders. With stable identity a reorder moves DOM nodes; without it Angular destroys and rebuilds them, losing focus, scroll position and any state inside those components.

It is required because trackBy was optional, awkward, and therefore skipped — which caused a whole family of performance complaints. Making it a compile error was the fix.

Trap: track $index is available and is wrong for anything that can be reordered, filtered or inserted into. The index is not a property of the item.

How do you cancel an in-flight HTTP request?

Unsubscribe — which switchMap does for you when a new value arrives. That is exactly what a search box needs, and it is the reason mergeMap is the wrong operator there: with mergeMap both requests stay open and the slower one can land last, overwriting newer results with older ones.

Follow-up: "How would you prove the cancellation works?" HttpTestingController exposes req.cancelled. Asserting on the rendered output cannot distinguish a cancelled stale request from lucky ordering.

What is an interceptor for?

One place that knows the API base URL, attaches the auth token, and converts a failure into your own error type. The argument for it over a wrapper function is reach: a wrapper only helps code that remembers to call it, while an interceptor is inside HttpClient and cannot be bypassed.

The security follow-up: "What must an interceptor be careful about?" Not attaching your token to requests for other origins. An interceptor sees everything, including the call to Stripe.

When would you reach for NgRx?

Rarely. A root-provided service holding signals gives shared state, derived values and one place for the rules, in about thirty lines.

A store buys a serialisable record of every change, time-travel debugging, effects as declarative pipelines, and a shape that stays legible across a large team. "Our state is complicated" is not the trigger. "We cannot tell what changed this, or in what order" is.

Follow-up if you claim NgRx experience: "How does it differ from Redux Toolkit?" No Immer — state.items.push(x) in an NgRx reducer is a real mutation and a real bug.

What lifecycle hooks do you use?

Honestly: almost none. ngOnInit when something needs an input at startup, because a constructor runs before inputs are set — reading a required input there throws NG0950.

Everything else has a better tool. computed replaces ngOnChanges and recalculates only for the input that changed. afterNextRender replaces ngAfterViewInit and is SSR-safe. DestroyRef replaces ngOnDestroy and can be injected in a plain function, which lets setup and teardown sit on adjacent lines instead of in two methods with a field between them.

The question behind the questions

Most of these have a shallow answer and a real one, and interviewers are listening for whether you have hit the failure mode. You know OnPush if you have been bitten by mutation. You know switchMap if you have seen stale results land last. You know updateValueAndValidity if you have stared at a form that would not submit.

If you have worked through this track, you have the demo application to point at — which is a better answer than any of the above, because it is specific.