Performance & Advanced Questions
Senior Angular interviews move past syntax and into systems thinking: how do you keep a large app fast, how does server-side rendering and hydration actually work, and how do you split a monolith into independently deployable pieces? These questions reward candidates who understand why Angular behaves the way it does and can reason about trade-offs under real-world constraints. The answers below use modern Angular 17–19 idioms — standalone components, signals, deferrable views, and the new SSR pipeline.
How do you optimize change detection in a large app?
The single biggest lever is OnPush change detection combined with immutable inputs and signals. Under OnPush, a component is only checked when one of its @Input references changes, an event fires inside it, an async pipe emits, or a signal it reads changes. This prunes huge swaths of the component tree from every check.
Signals make this even cleaner because reading a signal in a template registers a fine-grained dependency — when the signal changes, Angular marks only the components that consumed it.
import { ChangeDetectionStrategy, Component, computed, signal } from '@angular/core';
@Component({
selector: 'app-cart',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<p>Items: {{ count() }}</p>
<p>Total: {{ total() | currency }}</p>
<button (click)="add()">Add</button>
`,
})
export class CartComponent {
private items = signal<number[]>([]);
count = computed(() => this.items().length);
total = computed(() => this.items().reduce((a, b) => a + b, 0));
add() {
this.items.update((list) => [...list, 9.99]);
}
}
Other high-impact techniques: always use track in @for to avoid re-rendering unchanged rows, run hot non-UI work in runOutsideAngular to skip change detection entirely, and use trackBy-style stable identities for lists.
Zoneless change detection (
provideExperimentalZonelessChangeDetection()) removes Zone.js entirely and relies on signals and explicit notifications. It is the direction Angular is heading and a strong talking point.
What is hydration and why does it matter?
Server-side rendering (SSR) sends fully rendered HTML so the user sees content fast and search engines index it. Without hydration, Angular would throw that server HTML away and re-render from scratch on the client, causing a visible flicker and wasted work. Non-destructive hydration (provideClientHydration()) instead reuses the existing DOM, attaching event listeners and state on top of it.
import { bootstrapApplication } from '@angular/platform-browser';
import { provideClientHydration, withEventReplay } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent, {
providers: [provideClientHydration(withEventReplay())],
});
withEventReplay() captures clicks that happen before JavaScript loads and replays them once the app is interactive, so early taps are not lost. Angular 19 also adds incremental hydration, letting you hydrate @defer blocks on demand rather than all at once.
How do deferrable views improve performance?
@defer lazily loads a block of template — and its dependencies — only when a trigger fires. This shrinks the initial bundle and time-to-interactive without manual lazy-loading boilerplate.
@defer (on viewport) {
<app-heavy-chart [data]="metrics()" />
} @placeholder {
<div class="skeleton">Loading chart…</div>
} @loading (minimum 200ms) {
<app-spinner />
} @error {
<p>Failed to load chart.</p>
}
Triggers include on idle, on viewport, on interaction, on hover, on timer(2s), and when condition(). Use prefetch to fetch the bundle early while still deferring rendering.
| Trigger | Fires when |
|---|---|
on idle | Browser is idle (default) |
on viewport | Block scrolls into view |
on interaction | User clicks/keys the placeholder |
on hover | Pointer hovers the placeholder |
when expr | Signal/expression becomes truthy |
What are micro frontends and how does Angular support them?
Micro frontends split a large app into independently developed and deployed pieces, each owned by a different team. The dominant approach in Angular is Module Federation (via @angular-architects/module-federation on top of webpack, or native federation for esbuild). A shell app loads remote apps at runtime.
// Shell route that loads a remote at runtime
import { loadRemoteModule } from '@angular-architects/native-federation';
export const routes = [
{
path: 'checkout',
loadComponent: () =>
loadRemoteModule('checkout', './Checkout').then((m) => m.CheckoutComponent),
},
];
The trade-off is real: federation buys independent deploys and team autonomy but adds version-skew risk, shared-dependency negotiation, and operational complexity. Mention that for many apps, a well-structured Nx monorepo with library boundaries delivers most of the modularity benefits without the runtime cost.
How do you architect a large Angular application?
Discuss boundaries and ownership. A common pattern is feature-based folders with a clear separation between smart (container) and presentational components, a shared UI library, and a core layer for app-wide singletons. Lazy-load each feature route so the bundle scales with what the user actually visits.
export const routes = [
{ path: 'orders', loadChildren: () => import('./orders/routes') },
{ path: 'admin', loadChildren: () => import('./admin/routes') },
];
Enforce dependency direction with Nx tags or ESLint boundary rules so feature code cannot import another feature directly, only shared libraries. Use inject() with providedIn: 'root' services for tree-shakable singletons, and prefer signals for component state to keep change detection predictable.
Best practices
- Default to
OnPush(or zoneless) and drive UI with signals so Angular checks only what changed. - Always provide
trackin@forand stable keys to prevent needless DOM churn. - Enable SSR with
provideClientHydration(withEventReplay())for fast first paint and resilient early interactions. - Reach for
@deferto lazy-load heavy, below-the-fold, or interaction-gated UI. - Treat micro frontends as a team-scaling tool, not a default — prefer a monorepo with enforced boundaries until autonomy demands more.
- Lazy-load routes and run expensive non-UI work outside Angular to keep change detection cheap.