Components • Dependency Injection • Change Detection • Signals • RxJS • 2026

Angular Interview Questions

30 questions What each one tests, an answer frame, a spoken answer 36 min read

This page is for anyone facing an Angular round, from a first frontend job to a senior lead. Most rounds open with components, bindings and directives, then test dependency injection, lifecycle hooks and change detection, including OnPush and signals. Expect RxJS next: Observable versus Promise, the flattening operators, subjects and cleaning up subscriptions. Routing, guards, lazy loading, forms and interceptors fill the rest, and senior rounds add a performance problem, an upgrade story and a judgement call. Each question shows what the interviewer is checking, the shape of a strong answer and a short answer you can say out loud.

Search all questions by round, difficulty and level, or save the ones you want to practise.

Components 4 questions

Easy Technical round Fresher Practice question

1. What is an Angular component made of, and how does Angular know where to put it on the page?

What the interviewer is really testing:
Whether you understand the pieces of a component and how selectors connect them, rather than only generating files with the CLI.
Answer frame:

Class: a TypeScript class that holds the state and the logic.

Metadata: the @Component decorator gives a selector, a template and styles.

Placement: Angular renders it wherever the selector appears in a parent template, or at bootstrap for the root.

Styles: component styles are scoped to that component by default.

Sample spoken answer:

"A component is a TypeScript class with an @Component decorator on top. The class holds the data and the methods, and the decorator tells Angular three main things: the selector, the template, and the styles. The selector is like a custom HTML tag, say app-user-card. When Angular compiles a parent's template and finds that tag, it creates an instance of the component there and renders its template inside it. The root component is the one exception, because it's started directly at bootstrap. The template binds to the class's fields and methods, so when the data changes the view updates. And by default the styles I write in a component only apply to that component, because Angular scopes them by adding generated attributes to its elements, so a rule in one card doesn't leak into the rest of the app."

Red flag to avoid:

Describing a component only as 'the files the CLI makes' without explaining the decorator, the selector or how the template links to the class.

They may ask next:
  • What does view encapsulation do, and when would you turn it off?
  • Why should the class stay thin and push data fetching into a service?
Say it in 60 seconds
Medium Technical round Fresher, Mid-level Practice question

2. What's the difference between declaring components in an NgModule and using standalone components? Why did Angular move toward standalone?

What the interviewer is really testing:
Whether you know how modern Angular apps are wired together and can still read an older module-based codebase.
Answer frame:

NgModule: components are declared in a module, which imports and exports what its templates can use.

Standalone: each component lists its own dependencies in its imports array; no module needed.

Bootstrap: bootstrapApplication with provider functions such as provideRouter replaces the root module.

Why: less boilerplate, clearer dependencies, easier lazy loading; both styles can be mixed.

Sample spoken answer:

"In the older style, every component had to be declared in exactly one NgModule, and that module decided what its templates could use through its imports and exports. It worked, but you had to look in two places to understand one component, and shared modules tended to grow into big bags of everything. A standalone component lists what it needs directly in its own imports array: other components, directives, pipes. The app starts with bootstrapApplication and a list of providers like provideRouter and provideHttpClient instead of a root module. In recent versions standalone is the default. The wins are less boilerplate, dependencies you can see in one file, and simpler lazy loading, since a route can load a single component. Modules aren't gone, and the two mix fine, so in a migration I can convert one feature at a time."

Red flag to avoid:

Saying NgModules are removed and old apps must be rewritten, or not knowing where a standalone component gets its dependencies from.

They may ask next:
  • How would you lazy load a single standalone component from a route?
  • What would you do with a large SharedModule when migrating to standalone?
Say it in 60 seconds
Easy Technical round Fresher Practice question

3. How do a parent and child component talk to each other? And what do you do when two components aren't related?

What the interviewer is really testing:
Whether you know the standard data flow, down through inputs and up through outputs, and when to reach for a service.
Answer frame:

Down: the parent binds data to the child's inputs.

Up: the child emits an event through an output; the parent listens with event binding.

Direct access: a view query can reach a child instance, used sparingly.

Unrelated: a shared service holding a signal or a subject.

Sample spoken answer:

"Data goes down through inputs and events come up through outputs. The parent writes something like [user]="selectedUser" on the child tag, and the child declares user as an input, either with the @Input decorator or the newer input() function. When the child needs to tell the parent something, say the user clicked save, it declares an output, emits a value, and the parent listens with a (saved) event binding on the child tag and receives the emitted value. The child never reaches up into the parent, which keeps it reusable. If the parent needs to call a method on the child, it can grab it with a view query, but I use that sparingly. For components that aren't parent and child, like a header and a sidebar that both show the cart count, I put the state in a shared service, as a signal or a BehaviorSubject, and both components read from it."

Red flag to avoid:

Passing data between siblings by digging through the parent's instance, or using a global variable on window.

They may ask next:
  • How do you make an input required?
  • What's the risk of a child component changing an object it received as an input?
Say it in 60 seconds
Easy Technical round Fresher, Mid-level Practice question

4. Name the main lifecycle hooks in order. Why do we do setup in ngOnInit instead of the constructor?

What the interviewer is really testing:
Whether you know when inputs and child views are ready, which decides where each kind of code belongs.
Answer frame:

Constructor: plain class creation; dependencies are injected, inputs are not set yet.

Init: ngOnChanges when bound inputs change, then ngOnInit once.

Content and view: ngAfterContentInit, then ngAfterViewInit when child views exist.

Teardown: ngOnDestroy to clean up subscriptions, timers and listeners.

Sample spoken answer:

"The constructor isn't really a hook, it's just the class being created, and Angular uses it to inject dependencies. At that point the inputs haven't been set yet. Then ngOnChanges runs if the component has bound inputs, before ngOnInit and again every time one of those inputs changes, with the old and new values. ngOnInit runs once, after the first round of inputs, so that's where I start work that depends on inputs, like loading data for a given id. ngDoCheck runs on every check if I need custom change tracking. After that come ngAfterContentInit and ngAfterContentChecked for projected content, then ngAfterViewInit and ngAfterViewChecked once the component's own child views exist, which is when a normal view query result is ready to use. Finally ngOnDestroy, where I clean up subscriptions, timers and event listeners."

Red flag to avoid:

Reading input values in the constructor, or saying ngOnInit runs every time an input changes.

They may ask next:
  • Why can changing a bound value inside ngAfterViewInit cause an error in development mode?
  • When would you use ngOnChanges instead of reacting to an input some other way?
Say it in 60 seconds

Templates 4 questions

Easy Technical round Fresher Practice question

5. Walk me through the kinds of data binding in an Angular template, with a quick example of each.

What the interviewer is really testing:
Whether you can read and write template syntax fluently and know which way data flows in each binding.
Answer frame:

Interpolation: double curly braces put a value into text.

Property binding: square brackets set a DOM or input property from the class.

Event binding: round brackets call a class method when an event fires.

Two-way: banana-in-a-box combines both, a property plus a matching Change event.

Sample spoken answer:

"There are four I use every day. Interpolation, with double curly braces, drops a value into text, like a user's name in a heading. Property binding uses square brackets, so [disabled]="isSaving" sets the button's disabled property from my class. Data flows from the class to the view in both of those. Event binding goes the other way: (click)="save()" calls a method when the user clicks, and I can pass the event object in if I need it. Two-way binding, the banana in a box, [(ngModel)]="name", is really property binding plus event binding together. It works for any component with an input, say size, and a matching output called sizeChange, not just ngModel. There are also special forms: [class.active] and [style.width.px] toggle one class or one style, and [attr.colspan] sets an HTML attribute that has no matching DOM property."

Code:
<h2>Hello, {{ user.name }}</h2>
<button [disabled]="isSaving" (click)="save()">Save</button>
<input [(ngModel)]="user.name" />
<div [class.active]="isActive" [attr.aria-label]="label"></div>
Red flag to avoid:

Mixing up the brackets, or thinking two-way binding is special magic that only ngModel can do.

They may ask next:
  • Why does [colspan] fail on a table cell when [attr.colspan] works?
  • How would you make your own component support two-way binding?
Say it in 60 seconds
Medium Technical round Fresher, Mid-level Practice question

6. What's the difference between a structural directive and an attribute directive? Where does the new @if and @for syntax fit in?

What the interviewer is really testing:
Whether you know what the asterisk really does and are up to date with built-in control flow.
Answer frame:

Structural: adds or removes elements; the asterisk wraps the element in an ng-template.

Attribute: changes the look or behaviour of an existing element, like ngClass.

Control flow: @if, @for and @switch are built into the template syntax and replace ngIf, ngFor and ngSwitch.

track: @for requires a track expression so rows are reused instead of rebuilt.

Sample spoken answer:

"A structural directive changes the shape of the DOM: it adds, removes or repeats elements. ngIf and ngFor are the classic ones. The asterisk is shorthand: Angular wraps the element in an ng-template, and the directive decides whether and how many times to stamp that template out. That's also why you can only put one asterisk directive on an element. An attribute directive leaves the element in place and changes how it looks or behaves, like ngClass, ngStyle or a custom highlight directive. Newer Angular has built-in control flow: @if, @for and @switch blocks written straight in the template. They don't need imports, they read more like normal code, @if has a proper else branch, and @for makes you give a track expression, so Angular can match items by identity and reuse DOM rows instead of rebuilding the list."

Red flag to avoid:

Saying the asterisk is just a naming style, or thinking ngIf hides the element with CSS rather than removing it.

They may ask next:
  • What does ng-container give you, and why doesn't it add an element to the DOM?
  • What goes wrong in a big list when you track by index instead of by id?
Say it in 60 seconds
Medium Coding round Fresher, Mid-level Practice question

7. Write a custom attribute directive that highlights an element when the mouse is over it, with the colour passed in from the template.

What the interviewer is really testing:
Whether you can build a directive end to end: selector, input, host events and touching the element safely.
Answer frame:

Selector: an attribute selector in square brackets, so it sits on any element.

Input: alias the input to the selector name so the colour goes in one attribute.

Host events: listen to mouseenter and mouseleave on the host element.

Style: set the style on the host; a host binding or Renderer2 keeps it clean.

Sample spoken answer:

"I'd give the directive an attribute selector, appHighlight, so I can drop it on any element. To pass the colour in the same attribute, I alias an input to the selector name, so the template reads appHighlight="lightblue", and I fall back to yellow if it's empty. Then I listen to mouseenter and mouseleave on the host element with HostListener, and set or clear the background colour. I get the element through ElementRef. Writing to nativeElement directly is fine in the browser, but if the app also renders on the server I'd rather use a host binding on style.backgroundColor or Renderer2, so Angular manages the DOM for me. Usage is just a paragraph with appHighlight on it, and I'd import the directive where it's used."

Code:
import { Directive, ElementRef, HostListener, Input } from '@angular/core';

@Directive({ selector: '[appHighlight]', standalone: true })
export class HighlightDirective {
  @Input('appHighlight') color = '';

  constructor(private el: ElementRef<HTMLElement>) {}

  @HostListener('mouseenter') onEnter() {
    this.el.nativeElement.style.backgroundColor = this.color || 'yellow';
  }

  @HostListener('mouseleave') onLeave() {
    this.el.nativeElement.style.backgroundColor = '';
  }
}

// <p appHighlight="lightblue">Hover me</p>
Red flag to avoid:

Using document.querySelector inside the directive to find the element instead of working with the host element Angular hands you.

They may ask next:
  • How would you write the same thing with a host binding instead of touching nativeElement?
  • Why can direct DOM access be a problem with server-side rendering?
Say it in 60 seconds
Medium Technical round Fresher, Mid-level Practice question

8. What is the difference between a pure and an impure pipe? Why doesn't my filter pipe update when I push an item into the array?

What the interviewer is really testing:
Whether you understand how pipes are re-run and the reference-change rule that trips up many developers.
Answer frame:

Pure: the default; transform re-runs only when the input value or reference, or an argument, changes.

Impure: pure: false; transform runs on every change detection pass.

The push bug: mutating an array keeps the same reference, so a pure pipe skips it.

Fix: create a new array, or move the filtering into the component or a computed signal.

Sample spoken answer:

"Pipes are pure by default. A pure pipe only runs its transform again when its input changes: a new primitive value or a new object reference, or when one of its arguments changes. That makes it cheap, and it's why a pure pipe is a better choice than calling a method in the template. So if I push an item into an array, the array is still the same reference, and the pure filter pipe doesn't run again. The fix is to update immutably, like items = [...items, newItem], so the pipe sees a new reference. An impure pipe, marked pure: false, runs on every change detection cycle, which catches mutations but can be expensive in a big list. The async pipe is impure on purpose because it has to watch a subscription. I only write an impure pipe when I really need one, and I keep it very cheap."

Red flag to avoid:

Fixing the stale list by making the pipe impure without mentioning that it will now run on every change detection pass.

They may ask next:
  • Why is calling a method in a template usually worse than using a pure pipe?
  • How would you do the same filtering with a computed signal instead of a pipe?
Say it in 60 seconds

Dependency Injection 3 questions

Easy Technical round Fresher Practice question

9. What is dependency injection in Angular, and what does providedIn: 'root' actually do?

What the interviewer is really testing:
Whether you understand why Angular creates services for you and what one app-wide instance means.
Answer frame:

Idea: a class asks for what it needs; the injector creates and hands it over.

Asking: constructor parameters or the inject() function.

providedIn root: one shared instance for the whole app, created on first use.

Tree-shaking: if nothing injects it, the build can drop it.

Sample spoken answer:

"Dependency injection means a class doesn't build its own dependencies, it just asks for them, and Angular's injector creates them and hands them over. A component that needs the order service lists it in its constructor or calls inject(OrderService) in a field. The big win is that the component doesn't care how the service is made, so in a test I can swap in a fake with one provider line. providedIn: 'root' on the @Injectable decorator registers the service with the root injector. That gives me a single shared instance for the whole app, created the first time something asks for it. It's also tree-shakable: if nothing in the app injects it, the build can leave it out. That's why it's the default for most services, like an API client or an auth service that should hold one set of state."

Red flag to avoid:

Saying every component gets its own new service instance by default, or creating services with new inside components.

They may ask next:
  • Where can you call inject(), and where can't you?
  • How would you give each instance of a component its own copy of a service?
Say it in 60 seconds
Hard Technical round Mid-level, Senior Practice question

10. Angular has hierarchical injectors. If a service is provided in root, in a lazy-loaded route and in a component, which instance does a component get, and why?

What the interviewer is really testing:
Whether you can predict which instance is injected, which is the root of many 'my state disappeared' and 'two copies of the service' bugs.
Answer frame:

Two trees: element injectors from component providers; environment injectors for root and lazy routes.

Lookup: start at the requesting component, walk up the element tree, then the environment injectors.

Nearest wins: the first injector that has a provider supplies the instance.

Modifiers: self, skipSelf, optional and host change where the search starts or stops.

Sample spoken answer:

"There are two trees of injectors. Components and directives can have their own providers, which make element injectors. Then there are environment injectors: the root one, plus a child one for each lazy-loaded route that has its own providers. When a component asks for a service, Angular starts at that component's element injector and walks up through its parents. If none of them has a provider, it moves to the environment injectors, starting from the nearest one, up to root. The first match wins. So if the component itself lists the service in providers, it gets a brand new instance that lives and dies with that component. If not, but the lazy route provides it, everything in that route shares the route's copy, separate from the root one. That's a classic bug: someone adds a provider to a lazy route and suddenly the app has two copies of a service that should be a singleton."

Red flag to avoid:

Saying services are always singletons, or not realising that listing a service in a component's providers creates a new instance per component.

They may ask next:
  • What do the self, skipSelf and optional options do when you inject something?
  • How would you debug a NullInjectorError?
Say it in 60 seconds
Medium Technical round Mid-level Practice question

11. What are useClass, useValue, useFactory and useExisting? And when do you need an InjectionToken?

What the interviewer is really testing:
Whether you can configure DI beyond the defaults, for configuration objects, swappable implementations and test fakes.
Answer frame:

useClass: provide a different class for a token, like a mock or a new implementation.

useValue: hand over a ready-made value, like a config object.

useFactory: build the value with code, and inject what the factory needs.

useExisting: an alias, so two tokens give the same instance; InjectionToken for non-class values.

Sample spoken answer:

"A provider tells the injector what to hand out when someone asks for a token. useClass says use this class instead, so I can map a Logger token to a ConsoleLogger, or to a fake in tests. useValue hands out a value I already have, which is great for configuration. useFactory runs a function to build the value, and inside the factory I can call inject() to get other dependencies, for example building an API client from the config's base URL. useExisting creates an alias, so asking for the old token gives you the same instance as the new one, handy during a rename. An InjectionToken is what I use when the thing I'm providing isn't a class, like a config interface or a string. Interfaces don't exist at runtime, so they can't be a token, and the InjectionToken gives me a real, typed key."

Code:
export interface AppConfig { apiUrl: string; }
export const APP_CONFIG = new InjectionToken<AppConfig>('app.config');

providers: [
  { provide: APP_CONFIG, useValue: { apiUrl: '/api' } },
  { provide: Logger, useClass: ConsoleLogger },
  { provide: ApiClient, useFactory: () => new ApiClient(inject(APP_CONFIG).apiUrl) },
  { provide: OldLogger, useExisting: Logger },
]

// in a service
private config = inject(APP_CONFIG);
Red flag to avoid:

Trying to inject a TypeScript interface directly, or using useClass where useExisting was needed and ending up with two instances.

They may ask next:
  • What does multi: true do on a provider, and where does Angular use it?
  • Why can't you use a TypeScript interface directly as a DI token?
Say it in 60 seconds

Signals & Change Detection 4 questions

Medium Technical round Mid-level Practice question

12. How does Angular's default change detection work? What role does zone.js play?

What the interviewer is really testing:
Whether you understand what triggers a view update and why too much work in templates slows an app down.
Answer frame:

Trigger: zone.js patches async APIs and tells Angular when a task finishes.

Check: Angular walks the component tree from the root and compares each binding with its last value.

Update: only the DOM for bindings that changed is touched.

Dev check: a second pass in development catches values that change during a check.

Sample spoken answer:

"In the default setup, zone.js patches the browser's async APIs: DOM events, timers, promises, HTTP requests. When one of those finishes, zone.js tells Angular something might have changed, and Angular runs change detection. It walks the component tree from the root down and, for every binding in every template, compares the current value with the one it saw last time. Where they differ, it updates that bit of the DOM. Data flows one way, parent to child, in a single pass. In development mode it runs a second check straight after, and if any value changed between the two passes you get the ExpressionChangedAfterItHasBeenChecked error, which means something is updating state during rendering. The cost is that every event checks everything, so heavy getters or method calls in templates add up. OnPush and signals narrow it down, and newer apps can drop zone.js entirely and go zoneless, where signals, events and markForCheck schedule the checks."

Red flag to avoid:

Saying Angular rerenders the whole page on every change, or that zone.js watches variables for changes.

They may ask next:
  • What causes ExpressionChangedAfterItHasBeenCheckedError, and how do you fix it properly?
  • When would you run code outside Angular's zone, and why?
Say it in 60 seconds
Hard Technical round Mid-level, Senior Practice question

13. When exactly does an OnPush component get checked? Give me a case where it won't update and the developer thinks it's a bug.

What the interviewer is really testing:
Whether you can list the real triggers for OnPush and debug the stale-view bugs it causes, not just say 'it's faster'.
Answer frame:

Triggers: a new input reference, an event in its own template or its children, the async pipe, a signal read in the template, or markForCheck.

Skipped: otherwise Angular skips it and its whole subtree.

Classic bug: mutating an input object, or setting a field in a subscribe or setTimeout.

Fix: immutable updates, async pipe, signals, or markForCheck as a last resort.

Sample spoken answer:

"With OnPush, Angular skips the component and everything under it unless the component has been marked dirty. It gets marked when one of its inputs gets a new reference, when an event is handled in its own template or a child's, when an async pipe in its template receives a value, when a signal it reads in its template changes, or when I call markForCheck myself. The classic surprise is mutation. The parent does user.name = 'Sam' and the OnPush child still shows the old name, because the input is the same object reference. The other one is setting a plain field inside a subscribe callback or a setTimeout: the data changes but nothing marks the view. The fix I prefer is immutable updates and letting the template read from the async pipe or a signal. markForCheck works, but if I'm sprinkling it everywhere, the data flow is wrong."

Red flag to avoid:

Saying OnPush components only update when inputs change, forgetting events, the async pipe and signals.

They may ask next:
  • What's the difference between markForCheck and detectChanges?
  • Why is OnPush much easier to use with signals than with plain fields?
Say it in 60 seconds
Medium Technical round Fresher, Mid-level, Senior Practice question

14. What are signals in Angular? When do you use signal, computed and effect, and how are signals different from observables?

What the interviewer is really testing:
Whether you understand modern reactive state in Angular and pick the right tool for derived values and side effects.
Answer frame:

signal: a value you can read, set and update; readers are tracked.

computed: a read-only value derived from other signals, cached until they change.

effect: runs side effects, like logging or saving, when signals it reads change.

vs observables: signals always hold a current value and are synchronous; observables model events over time.

Sample spoken answer:

"A signal is a wrapper around a value that knows who reads it. I create one with signal(0), read it by calling it, and change it with set or update. computed builds a read-only value from other signals, like a cart total from the list of items. It's lazy and cached, and it only recalculates when one of its inputs changes. effect runs a side effect whenever the signals it reads change, like syncing to local storage or logging. I keep effects for talking to the outside world, not for copying one signal into another, because that's what computed is for. Because Angular knows exactly which templates read which signals, it can update just those components. Compared with observables, a signal always has a current value, reads are synchronous and there's nothing to unsubscribe. Observables are still better for streams of events over time, like HTTP calls or debounced search, and toSignal and toObservable connect the two."

Code:
items = signal<CartItem[]>([]);
total = computed(() => this.items().reduce((sum, i) => sum + i.price * i.qty, 0));

constructor() {
  effect(() => localStorage.setItem('cart', JSON.stringify(this.items())));
}

addItem(item: CartItem) {
  this.items.update(list => [...list, item]);
}
Red flag to avoid:

Using effect to keep a derived value in sync instead of computed, or mutating an array inside a signal and expecting readers to update.

They may ask next:
  • Why is setting one signal from inside an effect usually a design smell?
  • What does toSignal do with an observable that hasn't emitted yet?
Say it in 60 seconds
Hard Situational round Senior Practice question

15. Your team wants to rewrite every RxJS stream in the app as signals during the next sprint. How do you respond?

What the interviewer is really testing:
Whether you can separate hype from value, know where signals and RxJS each fit, and plan a safe, gradual migration.
Answer frame:

Agree on why: simpler state and finer updates are real wins; a full rewrite is not the goal.

Right tool: signals for state the template reads; RxJS for async event streams, cancellation and timing.

Gradual: new code signal-first; convert old code when touched, using toSignal and toObservable at the edges.

Safety: tests around each change, measured results, no big-bang sprint.

Sample spoken answer:

"I'd start by agreeing with the goal. Signals do make component state simpler and let Angular update only what changed. But rewriting every stream in one sprint is the wrong plan, for two reasons. First, not everything should become a signal. Debounced search, cancelling old HTTP calls with switchMap, retries, websockets: RxJS is still the better tool for streams of events over time. Signals are best for state the template reads. Second, a big-bang rewrite of working code is risky and delivers nothing users see. So I'd propose a rule: new components are signal-first, existing code gets converted when we're already changing it, and toSignal and toObservable bridge the two at the edges. We'd pick one busy screen as a pilot, measure it with the profiler, and write a short team guide from what we learn. That gets the benefits without betting a sprint on it."

Red flag to avoid:

Either agreeing to the big-bang rewrite or dismissing signals as hype, instead of placing each tool where it fits.

They may ask next:
  • Which parts of our app would you convert first, and why?
  • What would you measure to show the pilot was worth it?
Say it in 60 seconds

RxJS 5 questions

Easy Technical round Fresher, Mid-level Practice question

16. Observable versus Promise: what's the real difference, and why does Angular's HttpClient return an Observable?

What the interviewer is really testing:
Whether you know the practical differences, laziness, many values and cancellation, rather than a memorised table.
Answer frame:

Eager vs lazy: a promise starts at once; an observable starts on subscribe.

Values: one value for a promise; zero, one or many over time for an observable.

Cancel: unsubscribe stops the work; a promise can't be cancelled.

Operators: retry, debounce and switchMap compose cleanly on observables.

Sample spoken answer:

"A promise is eager: the work starts the moment you create it, it settles once with one value or an error, and you can't cancel it. An observable is lazy: nothing happens until someone subscribes, it can emit zero, one or many values over time, and unsubscribing tears the work down. For HttpClient that laziness matters. If I build an http.get and never subscribe, no request goes out. If I subscribe twice, two requests go out, which surprises people. And cancelling is real: when a user types a new search term, switchMap unsubscribes from the old request and the browser aborts it. On top of that I get operators like retry, debounceTime and catchError, which compose much more neatly than nested promise code. For a single one-off call a promise is fine, and I can convert with firstValueFrom when an API wants one."

Red flag to avoid:

Saying observables are just promises with more values, or not knowing that an HTTP observable does nothing until it's subscribed.

They may ask next:
  • What happens if two parts of the template both use the async pipe on the same HTTP observable?
  • When would you convert an observable to a promise, and what's the catch?
Say it in 60 seconds
Medium Coding round Mid-level, Senior Practice question

17. When would you use switchMap, mergeMap, concatMap or exhaustMap? Show me a search box that calls an API as the user types.

What the interviewer is really testing:
Whether you can pick the flattening operator from the behaviour you need, which is where many real RxJS bugs come from.
Answer frame:

switchMap: cancel the old inner call when a new value arrives; search and typeahead.

mergeMap: run all inner calls at once; independent work where order doesn't matter.

concatMap: queue them one after another in order; saves that must not overlap.

exhaustMap: ignore new values while one call is running; a login or submit button.

Sample spoken answer:

"All four take each value and map it to an inner observable, like an HTTP call. The difference is what happens when a new value arrives while an old call is still running. switchMap drops the old one and switches to the new one, which is exactly what a search box needs, because only the latest term matters and a slow old response must never overwrite a newer one. mergeMap keeps them all running in parallel, fine for independent work like uploading several files. concatMap waits and runs them in order, so I use it for saves that must hit the server in sequence. exhaustMap ignores new values until the current call finishes, which stops a double-clicked submit button sending two orders. For the search I also debounce the input, skip repeats with distinctUntilChanged, and catch errors inside the switchMap so one failed request doesn't kill the whole stream."

Code:
results$ = this.searchControl.valueChanges.pipe(
  debounceTime(300),
  distinctUntilChanged(),
  switchMap(term =>
    this.api.search(term).pipe(
      catchError(() => of([]))
    )
  )
);

// template: @for (r of results$ | async; track r.id) { <li>{{ r.name }}</li> }
Red flag to avoid:

Using mergeMap for a search box, which lets a slow old response land after a newer one and show the wrong results.

They may ask next:
  • Why does it matter whether catchError is inside or outside the switchMap?
  • Would switchMap be safe for a delete button? Why or why not?
Say it in 60 seconds
Medium Technical round Fresher, Mid-level Practice question

18. What's the difference between Subject, BehaviorSubject and ReplaySubject, and which would you use to hold the logged-in user in a service?

What the interviewer is really testing:
Whether you understand what late subscribers see, which decides whether shared state works at all.
Answer frame:

Subject: a multicast channel with no memory; late subscribers miss earlier values.

BehaviorSubject: needs a starting value and gives the latest one to every new subscriber.

ReplaySubject: replays the last N values to new subscribers.

Exposure: keep the subject private and expose asObservable().

Sample spoken answer:

"A Subject is both an observable and an observer, so I can push values into it and many subscribers get them. But it has no memory: if a component subscribes after a value was sent, it never sees that value. That's fine for one-off events like 'show a toast'. A BehaviorSubject needs a starting value and always remembers the latest one, so any new subscriber gets the current value straight away. That's what I'd use for the logged-in user, starting with null, because a component that loads later still needs to know who's logged in. A ReplaySubject replays the last N values to new subscribers and doesn't need a starting value, useful when you want a short history. In the service I keep the subject private and expose it with asObservable, so only the service can push new values. In newer code a signal often does the same job as the BehaviorSubject."

Red flag to avoid:

Using a plain Subject for current state and then wondering why components that load later show nothing.

They may ask next:
  • Why expose asObservable() instead of the subject itself?
  • When would you choose a signal over a BehaviorSubject for the same state?
Say it in 60 seconds
Medium Technical round Fresher, Mid-level Practice question

19. When do you need to unsubscribe in Angular, and what are the clean ways to do it?

What the interviewer is really testing:
Whether you know which subscriptions leak and use the idiomatic cleanup tools instead of manual bookkeeping everywhere.
Answer frame:

Leaks: long-lived streams like intervals, store or router events and form valueChanges outlive the component.

Safe-ish: HttpClient completes after one response, but the callback can still run after destroy.

Tools: async pipe, toSignal, takeUntilDestroyed, or take(1) for one value.

Symptom: handlers firing twice after navigating back, memory growing over time.

Sample spoken answer:

"Any subscription to a stream that lives longer than the component needs cleaning up. That's things like interval, router events, form valueChanges, or a subject in a root service. If I don't, the component stays in memory after it's destroyed and its callback keeps running, so after navigating back and forth I see handlers firing twice or three times. HttpClient calls complete after one response, so they don't leak forever, but the callback can still run after the user has left the page. The cleanest option is not to subscribe by hand at all: use the async pipe or toSignal and let Angular manage it. When I do need a manual subscribe, I add takeUntilDestroyed, which ties it to the component's lifetime, and I call it in the constructor or pass it a DestroyRef. For one value I use take(1) or first."

Red flag to avoid:

Saying you never need to unsubscribe because Angular does it automatically, or unsubscribing from everything by hand with a growing list of Subscription fields.

They may ask next:
  • Where can you call takeUntilDestroyed without passing a DestroyRef?
  • How would you spot a subscription leak in a running app?
Say it in 60 seconds
Medium Situational round Mid-level, Senior Practice question

20. In code review you see a component that subscribes inside another subscribe, copies the results into fields and never unsubscribes. The feature works. What do you do?

What the interviewer is really testing:
Whether you can explain real risks behind a style issue and coach a teammate toward the idiomatic fix without blocking them for no reason.
Answer frame:

Risks: race conditions, no cancellation, leaks and stale views with OnPush.

Fix: flatten with the right mapping operator and let the template subscribe.

Tone: explain why with a concrete failure, suggest the change, offer to pair.

Scale: if it's common, fix the pattern with a lint rule or team guide.

Sample spoken answer:

"I wouldn't just write 'use RxJS properly'. I'd point to what can actually go wrong. Nested subscribes have no cancellation, so if the outer value changes quickly, several inner requests race and the last one to return wins, which may be the wrong data. The inner subscription is never cleaned up, so it can leak and keep running after the user leaves. And copying into fields means an OnPush component may not update. Then I'd suggest the fix: flatten the chain with switchMap, or concatMap if order matters, and expose one observable that the template reads with the async pipe, or turn it into a signal with toSignal. I'd write that snippet in the review so it's easy to accept, and offer to pair if they want. If it's urgent and working, I might approve with a follow-up ticket. If I see the pattern a lot, it goes into our team guide and lint rules."

Red flag to avoid:

Blocking the change with a vague comment about best practice, or approving it without mentioning the race condition and the leak.

They may ask next:
  • Which mapping operator would you suggest if the inner call is a save that must not be skipped?
  • Would you block the merge, or approve with a follow-up? What decides it?
Say it in 60 seconds

Routing 2 questions

Medium Technical round Fresher, Mid-level Practice question

21. What route guards does Angular have, and how would you write one that sends logged-out users to the login page?

What the interviewer is really testing:
Whether you know the guard types, write modern functional guards, and understand that guards are not real security.
Answer frame:

Types: canActivate, canActivateChild, canDeactivate, canMatch; resolvers fetch data before a route.

Functional: a plain function that uses inject() and returns true, false or a UrlTree.

Redirect: return a UrlTree to the login page instead of navigating inside the guard.

Security: guards only protect the UI; the server must check every request.

Sample spoken answer:

"canActivate decides if a route can be entered, canActivateChild does the same for child routes, canDeactivate asks whether you can leave, which is great for unsaved form warnings, and canMatch decides whether a route even matches, so a lazy chunk isn't downloaded for users who can't use it. Resolvers aren't guards but sit alongside them to load data before the route shows. In modern Angular a guard is just a function. For the login case I inject the auth service and the router, return true if the user is logged in, and otherwise return a UrlTree for the login page, with the original URL as a query param so I can send them back. Returning a UrlTree is cleaner than calling navigate inside the guard. And I always say this: a guard hides screens, it doesn't protect data. The API still has to check the token on every request."

Code:
export const authGuard: CanActivateFn = (route, state) => {
  const auth = inject(AuthService);
  const router = inject(Router);
  return auth.isLoggedIn()
    ? true
    : router.createUrlTree(['/login'], { queryParams: { returnUrl: state.url } });
};

// { path: 'orders', component: OrdersPage, canActivate: [authGuard] }
Red flag to avoid:

Treating a route guard as the security layer, or calling router.navigate inside a guard and returning false.

They may ask next:
  • How would you write a canDeactivate guard that warns about unsaved changes?
  • Why might you choose canMatch over canActivate for a lazy-loaded admin area?
Say it in 60 seconds
Medium Situational round Fresher, Mid-level Practice question

22. After a deploy, users who refresh on a link like /orders/42 get a server 404, but clicking through from the home page works fine. What's going on, and how do you fix it?

What the interviewer is really testing:
Whether you understand client-side routing versus the server, a very common real deployment problem.
Answer frame:

Cause: in-app navigation never hits the server; a refresh asks the server for /orders/42, which has no such file.

Fix: configure the server to return index.html for unknown paths, but not for API or asset paths.

Check: the base href matches where the app is served from.

Fallback: hash-based URLs work without server changes, at the cost of uglier links.

Sample spoken answer:

"This is the client-side routing classic. When users click around, the Angular router changes the URL in the browser without asking the server, so everything works. But on a refresh or a pasted link, the browser asks the server for /orders/42, and there's no file there, so the server says 404. The fix is on the server or hosting side: for any path that isn't a real file, serve index.html, and let Angular's router take over and show the right page. I'd be careful that API routes and missing assets still return real 404s, otherwise a missing script comes back as HTML and the error gets confusing. I'd also check the base href, especially if the app is served from a sub-path. If we truly couldn't change the server, hash-based URLs avoid the problem, but I'd treat that as a last resort because the links are uglier."

Red flag to avoid:

Blaming the Angular router config or suggesting a page reload trick, instead of recognising it's the server's fallback rule.

They may ask next:
  • Why would it be a problem to send index.html for every missing file, including scripts?
  • Why does it matter if the app is served from a sub-folder instead of the root?
Say it in 60 seconds

Performance 2 questions

Medium Technical round Mid-level, Senior Practice question

23. How does lazy loading work in Angular, and how is it different from using a @defer block in a template?

What the interviewer is really testing:
Whether you can shrink the first download at both the route level and inside a page.
Answer frame:

Route level: loadComponent or loadChildren with a dynamic import; the build puts it in its own chunk.

Preloading: optionally fetch lazy chunks in the background after the first page loads.

@defer: lazy loads part of a template, with triggers such as on viewport, on idle or on interaction.

UX: placeholder and loading blocks keep the page stable while it loads.

Sample spoken answer:

"Lazy loading at the route level means a feature's code isn't in the main bundle. In the route I write loadComponent or loadChildren with a dynamic import, the build splits that code into its own chunk, and the browser only downloads it the first time someone visits the route. So the admin area never slows down the login page. If I want the next pages to feel instant, I can add a preloading strategy that fetches lazy chunks in the background after the first page has loaded. @defer works inside a single template. I wrap a heavy piece, like a chart or a comments widget, in a @defer block with a trigger such as on viewport or on idle, and Angular loads its components only when the trigger fires. I give it a placeholder block and a loading block so the layout doesn't jump. Together they cut the first load a lot."

Red flag to avoid:

Saying lazy loading makes pages load faster everywhere, without mentioning the delay on first visit or how preloading addresses it.

They may ask next:
  • What has to be true of a component for @defer to actually split it out of the bundle?
  • How would you check that a route really became its own chunk?
Say it in 60 seconds
Hard Technical round Mid-level, Senior Practice question

24. A page with a large table feels sluggish when users type or scroll. How do you find the cause in Angular and make it fast?

What the interviewer is really testing:
Whether you measure before fixing and know the Angular-specific levers, not just generic web tips.
Answer frame:

Measure: Angular DevTools profiler for change detection cost; the browser Performance tab for the rest.

Less checking: OnPush or signals, and move scroll or mousemove listeners outside the zone.

Less work per check: pure pipes or computed instead of template method calls; good track keys.

Less DOM: virtual scrolling or pagination, and @defer for heavy parts.

Sample spoken answer:

"I'd measure first. The Angular DevTools profiler shows how often change detection runs and which components take the time, and the browser's Performance tab shows whether it's scripting, layout or painting. In tables the usual culprits are clear. Every keystroke triggers a full check, and each row calls methods in its template, like formatPrice(row), which run on every pass. I'd switch those to pure pipes or computed signals, and make the table OnPush or signal-based so typing in a search box doesn't recheck every row. I'd make sure the list tracks by a stable id, so filtering reuses rows instead of rebuilding the DOM. If there are thousands of rows, I'd render only what's visible with a virtual scroll viewport, or paginate. And for scroll or mousemove listeners that don't change state, I'd run them outside Angular's zone so they don't trigger change detection at all."

Red flag to avoid:

Jumping straight to fixes without measuring, or not knowing that method calls in templates run on every change detection pass.

They may ask next:
  • How would you prove the fix worked, not just that it feels faster?
  • What does running code outside the Angular zone mean, and how do you get back in when you need to update the view?
Say it in 60 seconds

Forms & HTTP 3 questions

Easy Technical round Fresher, Mid-level Practice question

25. Reactive forms or template-driven forms: how are they different, and which do you pick for a large form?

What the interviewer is really testing:
Whether you know where the form model lives in each approach and can justify a choice for real work.
Answer frame:

Template-driven: ngModel in the template; Angular builds the model for you.

Reactive: FormGroup and FormControl built in the class; the template just binds to them.

Strengths: reactive is explicit, typed, easy to test and handles dynamic fields with FormArray.

Choice: template-driven for small simple forms, reactive for anything big or dynamic.

Sample spoken answer:

"In template-driven forms the template is the source of truth. I put ngModel on inputs, add validation attributes like required, and Angular builds the form model behind the scenes. It's quick for a login box or a small settings form. In reactive forms I build the model in the class with FormGroup, FormControl or FormBuilder, and the template just binds to it with formGroup and formControlName. That makes everything explicit: I can read and set values synchronously, listen to valueChanges as an observable, add or remove fields at runtime with FormArray, and unit test the logic without rendering anything. With typed forms, the values carry real types in the class too, instead of any. For a large form, like a multi-step checkout with conditional fields, I'd pick reactive every time. Mixing both styles in one form is what I'd avoid."

Red flag to avoid:

Saying template-driven forms can't be validated, or picking reactive forms for no reason other than 'it's more advanced'.

They may ask next:
  • How would you add a field at runtime, like another phone number?
  • Why shouldn't you use ngModel on a control that is also bound to a reactive form?
Say it in 60 seconds
Medium Coding round Fresher, Mid-level Practice question

26. Write a custom validator for a reactive form that checks the password and confirm-password fields match.

What the interviewer is really testing:
Whether you know a validator is just a function returning errors or null, and that cross-field checks belong on the group.
Answer frame:

Shape: a ValidatorFn takes a control and returns an errors object or null.

Cross-field: put it on the FormGroup, not one control, so it can see both values.

Show it: read the group's error in the template, usually once the confirm field is touched.

Async: server checks use an async validator that returns an observable.

Sample spoken answer:

"A validator is just a function. It gets a control and returns null if it's valid, or an object describing the error if it isn't. Because this check compares two fields, I attach it to the form group rather than to either control, so it can read both values. Inside, I get password and confirm from the group and return a passwordsMismatch error when they differ. Then in the template I show a message when the group has that error and the confirm field has been touched, so the user isn't shouted at before they type. For checks that need the server, like whether a username is taken, I'd write an async validator that returns an observable. Angular only runs async validators once the sync ones pass, and I'd debounce or use updateOn blur so I'm not calling the API on every keystroke."

Code:
import { AbstractControl, ValidationErrors, ValidatorFn, Validators } from '@angular/forms';

export const passwordsMatch: ValidatorFn = (group: AbstractControl): ValidationErrors | null => {
  const pass = group.get('password')?.value;
  const confirm = group.get('confirm')?.value;
  return pass === confirm ? null : { passwordsMismatch: true };
};

// in the component
form = this.fb.group(
  {
    password: ['', [Validators.required, Validators.minLength(8)]],
    confirm: ['', Validators.required],
  },
  { validators: passwordsMatch }
);
Red flag to avoid:

Putting the match check on the confirm control and reading the other field through a component variable, so it goes stale when the password changes.

They may ask next:
  • How would you make the error show on the confirm field itself rather than on the group?
  • What does updateOn: 'blur' change, and when is it worth using?
Say it in 60 seconds
Hard Coding round Mid-level, Senior Practice question

27. What is an HTTP interceptor in Angular? Write one that adds an auth token to every request and handles a 401.

What the interviewer is really testing:
Whether you can centralise cross-cutting HTTP logic correctly, including immutable requests, error handling and injection context.
Answer frame:

Role: middleware for every HttpClient call: auth headers, logging, errors, retries.

Immutable: requests can't be edited, so clone them with the change.

Register: provideHttpClient(withInterceptors([...])); they run in the order listed.

401: catch it, refresh once or log out, and never loop on the refresh call itself.

Sample spoken answer:

"An interceptor sits between HttpClient and the network, so every request and response passes through it. That's the right place for things every call needs: adding an auth header, logging, turning errors into user messages. In modern Angular it's a function that gets the request and a next handler. Requests are immutable, so I clone it with setHeaders to add the bearer token, then pass the clone on. I register it with provideHttpClient and withInterceptors, and they run in the order I list them. For errors I pipe catchError onto next. On a 401 I either try a token refresh once and retry, or log the user out. One detail matters: I call inject at the top of the function, not inside the catchError callback, because inject only works in the injection context. And the refresh request must skip this logic, or a failing refresh can loop forever."

Code:
export const authInterceptor: HttpInterceptorFn = (req, next) => {
  const auth = inject(AuthService);
  const token = auth.token();
  const authReq = token
    ? req.clone({ setHeaders: { Authorization: `Bearer ${token}` } })
    : req;

  return next(authReq).pipe(
    catchError((err: HttpErrorResponse) => {
      if (err.status === 401) auth.logout();
      return throwError(() => err);
    })
  );
};

// app config: provideHttpClient(withInterceptors([authInterceptor]))
Red flag to avoid:

Trying to set a header on the original request object, or sending the auth token to every domain including third-party APIs.

They may ask next:
  • If five requests fail with 401 at the same moment, how do you make sure only one token refresh happens?
  • How would you skip the interceptor for a call to a third-party API?
Say it in 60 seconds

Real Work 3 questions

Hard Behavioral round Mid-level, Senior Practice question

28. Tell me about a memory leak or a runaway subscription you tracked down in an Angular app. How did you find it?

What the interviewer is really testing:
Whether you've debugged a real RxJS or lifecycle problem with evidence, and fixed the pattern rather than one instance.
Answer frame:

Symptom: what users or monitoring saw, like duplicate calls or a tab slowing down.

Evidence: how you narrowed it down: network tab, heap snapshots, logging subscriptions.

Cause: the exact subscription and why it outlived its component.

Prevent: the fix, plus a lint rule, a pattern or a review habit so it doesn't come back.

Sample spoken answer:

"At my last company we had a dashboard that got slower the longer people kept it open, and support said it sometimes saved the same filter twice. In the network tab I saw the save call fire once per visit to the page, so two visits meant two calls. That told me something was still subscribed from an earlier visit. I took heap snapshots before and after navigating away, and old instances of the filter component were still in memory. The cause was a subscription to form valueChanges merged with a stream from a root service, subscribed in ngOnInit with no teardown. The service lived forever, so it kept every old component alive. I moved the logic into the template with the async pipe, and where we needed a manual subscribe I added takeUntilDestroyed. Then I searched the codebase for the same pattern, fixed several more, and we added a lint rule and a review checklist item."

Red flag to avoid:

A story where the fix was a page refresh or a random guess, with no evidence of how the cause was found.

They may ask next:
  • How do heap snapshots show that a destroyed component is still in memory?
  • What lint rule or team habit would you put in place to stop this coming back?
Say it in 60 seconds
Hard Behavioral round Senior Practice question

29. Tell me about upgrading an Angular app across one or more major versions. How did you plan it, and what broke?

What the interviewer is really testing:
Whether you can manage a risky upgrade in a live product: sequencing, third-party libraries, testing and communication.
Answer frame:

Plan: one major version at a time, following the official update guide and running the automated migrations.

Blockers: check third-party libraries first; they are usually the slowest part.

Safety: tests and a smoke checklist between each step; ship behind small, reversible releases.

Result: what broke, how you fixed it and what you'd do earlier next time.

Sample spoken answer:

"In my last role we had an app a few major versions behind, and it was blocking security updates. First I listed every third-party library and checked which versions supported each Angular release, because that's usually the real bottleneck. Two libraries were abandoned, so we replaced them before touching Angular itself. Then we went one major version at a time with ng update, which runs the automated code migrations, and after each step we ran the unit tests, the end-to-end suite and a manual smoke test of the key flows before merging. What broke was mostly around the edges: a date picker's styles, some deprecated APIs we had to replace by hand, and a few tests that relied on old timing behaviour. We shipped each step separately so a problem could be rolled back. Next time I'd keep the app current every release, because small upgrades are far cheaper than one big jump."

Red flag to avoid:

Describing a big-bang jump across several versions with no testing plan, or ignoring third-party libraries until the last minute.

They may ask next:
  • Why go one major version at a time instead of jumping straight to the latest?
  • How did you keep feature work moving while the upgrade was in progress?
Say it in 60 seconds
Medium Behavioral round Mid-level, Senior Practice question

30. Tell me about a reusable component you built for other Angular developers on your team. What decisions did you make about its API?

What the interviewer is really testing:
Whether you design components for other developers: clear inputs and outputs, content projection, forms support and accessibility.
Answer frame:

Need: what was being copied or done inconsistently before.

API: inputs, outputs and content projection kept small and predictable.

Forms: ControlValueAccessor if it acts like an input, so it works with reactive forms.

Adoption: docs, examples, OnPush, and feedback from the first teams that used it.

Sample spoken answer:

"At my last company every team had its own version of a searchable dropdown, each with different bugs and keyboard behaviour. I built one shared component. I kept the API small: an items input, a function input to get each item's label, and an output when the selection changes. Anything visual, like how an option looks, went through content projection with a template the caller passes in, so I didn't need a new input for every design request. The important decision was implementing ControlValueAccessor, so it works with formControlName like a normal input, including disabled state and touched status for validation. I made it OnPush, added proper keyboard support and ARIA roles, and wrote a short page of examples. Two teams tried it first and their feedback changed the API before we rolled it out, which saved us breaking changes later."

Red flag to avoid:

A component with dozens of boolean inputs for every case, or one that can't be used inside a reactive form.

They may ask next:
  • What methods does ControlValueAccessor need, and what does each one do?
  • How do you change a shared component's API without breaking every team using it?
Say it in 60 seconds
Were you asked something else? Share it A person checks every question before it goes on the site. No name is shown.
For the call itself

The questions above are the prep. The call has ten more.

ClapAssist is an AI interview assistant for Mac and Windows. It listens to the interview on your computer and shows you what to say, in short lines you can read while you talk. Your resume and notes are never stored on our servers. It stays out of screen share on every plan; only you can see it.

Download ClapAssist with 10 free minutes
Mac and Windows · Stays out of screen share · No card