Introduction
An interview for a Frontend or Angular Developer position is never a simple test of syntax. Whoever conducts the interview wants to understand how you think when faced with a problem, how much you deeply know the tools you use every day, and how your choices change when you do scenario goes from "one component" to "an enterprise application used by hundreds of thousands of users".
This guide collects 25 real questions, organized into four levels of seniority — Junior, Mid-level, Senior and Expert/Lead — with comprehensive answers that you can use both to prepare for a interview both, if you are on the other side of the table, to conduct one yourself in a way structured.
The first level questions test the foundations (HTML, CSS, JavaScript, TypeScript, Basic Angular); those of the intermediate levels enter RxJS, components, services, routing and state management; the Senior ones touch on change detection, rendering, advanced performance, testing and design patterns; the Expert/Lead ones address large application architectures, microfrontend, SSR, security and the technical trade-offs that a lead must be able to argue, not just knowing.
Topics: #Angular #Frontend #JobInterview #TypeScript #JavaScript #RxJS #WebDevelopment #CareerAdvice #SoftwareArchitecture #TechnicalInterview
How This Guide is Structured
Each question is designed to reflect what is actually asked in technical interviews, not "textbook" questions isolated from context. The answers are not limited to the definition formal: they explain why the answer is that, when the rule has exceptions, and what an experienced interviewer expects to hear to distinguish a textbook answer from an answer from someone who actually solved the problem in production.
- Junior level (6 questions): fundamentals of HTML, CSS, JavaScript, TypeScript and Angular.
- Mid-level level (6 questions): RxJS, components, services, routing, state management, basic performance.
- Senior level (7 questions): architecture, change detection, rendering, advanced performance, testing, design pattern.
- Expert/Lead level (6 questions): large application architectures, scalability, microfrontends, SSR, security, technical trade-offs.
Junior Level — HTML, CSS, JavaScript, TypeScript and Angular Fundamentals
At this level, the interviewer's goal is not to find the mistake, but to understand if you have solid foundations to build on. The best answers are those that, in addition to the definition, they explain the practical impact of knowledge.
1. What is the difference between block-level and inline HTML elements, and why is it important to know?
The block-level elements (like <div>,
<section>, <p>) always occupy the entire width
available of the parent and start on a new line, accepting width,
height and margins/padding on all sides. The elements inline
(like <span>, <a>, <strong>) occupy
only the space necessary for the content, they are arranged in line with the surrounding text and
ignore width/height, as well as handling margins in a special way
vertical.
Knowing it is important because it explains very common layout bugs: a <span>
to which you set width and nothing happens, or an element that "doesn't line up"
as you expect. With display: flex, grid and inline-block
this classic distinction is blurring, but understanding the default behavior remains fundamental
to predict how an element will behave before even applying CSS.
2. What are the CSS box model and the differences between content-box and border-box?
The box model describes how the browser calculates the final dimensions of a
element: content (the content), padding (internal space),
border (border) and margin (outer space), concentric to each other
in the other. The box-sizing property determines how width and
height are interpreted:
/* content-box (default del browser): width/height si riferiscono SOLO al contenuto.
padding e border si sommano, ingrandendo la dimensione finale renderizzata. */
.box-legacy {
box-sizing: content-box;
width: 200px;
padding: 20px;
border: 5px solid black;
/* larghezza finale renderizzata: 200 + 20*2 + 5*2 = 250px */
}
/* border-box: width/height includono padding e border.
La dimensione dichiarata è la dimensione finale renderizzata. */
.box-modern {
box-sizing: border-box;
width: 200px;
padding: 20px;
border: 5px solid black;
/* larghezza finale renderizzata: 200px, esattamente come dichiarato */
}
In practice, almost all modern projects set
*, *::before, *::after { box-sizing: border-box; } globally just because
makes layout calculations predictable, preventing adding padding from "breaking" a grid
already sized.
3. What is the difference between var, let, and const in JavaScript, and what problems does let solve?
var has function scope (or global scope if declared outside
functions) and suffers from hoisting with initialization to undefined, which
allows you to "use it before you declare it" without errors — a buggy behavior
silent. let and const instead have block scope
(visible only inside the curly brackets in which they are declared) and live in one
temporal dead zone: accessing it before the declaration launches a
ReferenceError explicit instead of returning silently
undefined.
// Il classico bug da var dentro un loop asincrono
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log('var:', i), 10);
}
// Stampa: var: 3, var: 3, var: 3
// perché var è condivisa da tutte le iterazioni (una sola variabile, function-scoped)
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log('let:', i), 10);
}
// Stampa: let: 0, let: 1, let: 2
// perché let crea un nuovo binding per ogni iterazione del loop
const prevents binding reassignment (does not make object immutable:
an array or object declared with const can still be mutated to its
internal). The rule of thumb that almost all teams follow: const by default,
let only when reassignment is needed, var never in new code.
4. What are closures in JavaScript and what are they for in practice?
A closure is created when a function "remembers" scope variables in which it was defined, even after that scope has finished executing. In practice, the internal function maintains a live reference to the variables of the external function.
function createCounter() {
let count = 0; // variabile "chiusa" nella closure
return {
increment: () => ++count,
decrement: () => --count,
getValue: () => count,
};
}
const counter = createCounter();
counter.increment();
counter.increment();
console.log(counter.getValue()); // 2
// count non è accessibile dall'esterno: è uno stato privato reso possibile dalla closure
console.log(counter.count); // undefined
Closures are everywhere in real Angular code: every time you pass a callback function
to a subscribe(), to a setTimeout, or define a
computed()/effect() of a Signal, that function "closes" on
component variables. Understanding closures also explains a frequent bug: capture by
reference a variable that changes (e.g. the index of a loop) instead of the expected value at
time of the call.
5. What are generics in TypeScript and why are they useful?
The generics allow you to write functions, classes and interfaces that
they work with different types without losing type safety, substituting the alternative
worst — use any, which effectively turns off type-checking.
// Senza generics: perdiamo informazione di tipo, il chiamante deve fare un cast manuale
function wrapInArrayUnsafe(value: any): any[] {
return [value];
}
const result = wrapInArrayUnsafe('hello'); // result è any, nessun autocompletamento
// Con generics: il tipo si propaga automaticamente dall'input all'output
function wrapInArray(value: T): T[] {
return [value];
}
const strings = wrapInArray('hello'); // TypeScript inferisce string[]
const numbers = wrapInArray(42); // TypeScript inferisce number[]
// Un caso reale: un servizio HTTP generico
class ApiService {
constructor(private endpoint: string) {}
getAll(): Observable {
return this.http.get(this.endpoint);
}
}
const productsApi = new ApiService('/api/products');
// productsApi.getAll() restituisce Observable, tipizzato correttamente
In Angular generics are everywhere: Observable,
signal, Component in tests, i
Repository NestJS side. Knowing how to use them first hand (and not only that
recognize them) is what distinguishes those who write robust typed code from those who simply
copy existing patterns.
6. What is an Angular component and what are its main elements?
An Angular component is a TypeScript class decorated with @Component that
controls a portion of the user interface. Its main elements are:
- Decorator
@Component: metadata describing the selector, template, and style of the component. - Template: HTML (inline or in separate file) with binding, directives and interpolation.
- Class: contains state (properties, signals) and behavior (methods, lifecycle hooks).
- Styles: CSS with scope encapsulated to the default component (View Encapsulation).
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-greeting',
standalone: true, // non richiede più NgModule dal 2023 in poi
template: `
Ciao, {{ name() }}!
Cambia nome
`,
styles: [`h2 { color: var(--color-primary); }`],
})
export class GreetingComponent {
name = signal('Mondo');
changeName(): void {
this.name.set('Angular');
}
}
From 2023 (Angular 14+ for standalone components, then default from Angular 17) a component
it no longer needs to be declared in a NgModule: it directly declares the
own dependencies via imports in the decorator. It's the pattern you'd expect
be used in any new project today.
Mid-level — RxJS, Components, Services, Routing and State Management
At this level the interviewer checks whether you know how to connect the concepts: it is not enough knowing what an Observable is, you need to know which RxJS operator to use in which scenario, and because a wrong choice causes real bugs (race conditions, memory leaks, requests duplicates).
7. What is the difference between switchMap, mergeMap, concatMap, and exhaustMap in RxJS, and when to use each?
All four are "flattening" operators that manage an Observable of Observable, but with opposing competition strategies:
| Operator | Behavior | Typical use case |
|---|---|---|
switchMap | Delete previous uncompleted request when new one arrives | Search field with autocomplete (last input only count) |
mergeMap | Execute all requests in parallel, without deleting anything | Multiple uploads of files independent of each other |
concatMap | Enqueues requests, executes the next one only after the previous one is completed | Operations that must occur in strict order (e.g. sequential writing on a log) |
exhaustMap | Ignore new events until the current one is completed | Submit button — avoid double click submissions multiples |
// L'errore più comune: usare mergeMap per una ricerca con autocomplete
searchInput.valueChanges.pipe(
debounceTime(300),
mergeMap(query => this.api.search(query)), // ❌ le risposte possono arrivare fuori ordine!
).subscribe(results => this.results.set(results));
// Se l'utente digita velocemente "an" poi "angular", e la richiesta per "an"
// impiega più tempo a rispondere di quella per "angular", l'utente vede
// i risultati sbagliati (quelli di "an") sovrascrivere quelli corretti.
// La scelta corretta: switchMap cancella la richiesta obsoleta
searchInput.valueChanges.pipe(
debounceTime(300),
switchMap(query => this.api.search(query)), // ✅ solo l'ultima richiesta conta
).subscribe(results => this.results.set(results));
A solid mid-level answer explicitly mentions this sorting bug: it's the reason
real so switchMap is almost always the correct choice for the search, not a
arbitrary rule to be learned by heart.
8. How does Dependency Injection work in Angular and what is the difference between providedIn: 'root' and component-level providers?
Angular maintains a hierarchy of injector: a root-level
application, and one for each component (and its children, if not overridden). When a
component requires a dependency in the constructor (or with inject()), Angular
searches for a provider up the hierarchy from the component injector to the root.
// providedIn: 'root' — un'unica istanza condivisa in tutta l'applicazione (singleton)
@Injectable({ providedIn: 'root' })
export class AuthService {
private currentUser = signal(null);
}
// providers a livello di componente — una nuova istanza per ogni istanza del componente
@Component({
selector: 'app-product-form',
providers: [FormStateService], // ogni ottiene la SUA istanza
standalone: true,
})
export class ProductFormComponent {
private formState = inject(FormStateService);
}
The choice has real consequences: if you put a state that must be shared (e.g.
authentication) in the providers of a component instead of in
providedIn: 'root', each component instance will have an isolated state — a bug
classic "why doesn't my service see updated data?" which almost always arises from this
confusion between injector scopes.
9. What are Route Guards and what types exist in modern Angular?
The guard are functions (in modern Angular, pure functions, no longer classes with interfaces) that decide whether a navigation can proceed, should be redirected, or blocked. The main ones are:
CanActivateFn: decides whether a route can be activated (e.g. authentication check).CanActivateChildFn: as above, but applied to child routes.CanDeactivateFn: decides whether a route can be left (e.g. "unsaved changes" warning).CanMatchFn: decides whether a route can be matched at all, useful for hiding entire lazy-loaded sections from users without permissions.ResolveFn: is not technically a guard, but it preloads data before the route is activated.
export const authGuard: CanActivateFn = (route, state) => {
const auth = inject(AuthService);
const router = inject(Router);
if (auth.isAuthenticated()) return true;
router.navigate(['/login'], { queryParams: { returnUrl: state.url } });
return false;
};
// registrazione nelle route
export const routes: Routes = [
{ path: 'dashboard', component: DashboardComponent, canActivate: [authGuard] },
];
10. How would you handle shared state between unrelated components in a medium-sized Angular application?
For unrelated components (which have no direct parent-child relationship), the solution
standard is a shared service with scope providedIn: 'root'
which exposes state via Signal (or, in older codebases, via
BehaviorSubject).
@Injectable({ providedIn: 'root' })
export class CartStateService {
private readonly _items = signal([]);
readonly items = this._items.asReadonly(); // esposto in sola lettura
readonly total = computed(() =>
this._items().reduce((sum, i) => sum + i.price * i.quantity, 0)
);
addItem(item: CartItem): void {
this._items.update(items => [...items, item]);
}
}
For medium-sized applications this pattern is enough: it is simple, type-safe, responsive, and requires no external dependencies. A library like NgRx only becomes justified when concrete needs emerge — time-travel debugging, actions traceable with DevTools, Complex state logic shared across dozens of features — not "because it's the standard enterprise". Introducing it prematurely adds boilerplate without commensurate benefits.
11. What are Signals in Angular and how do they differ from an RxJS Observable?
A Signal is a reactive container of a value that notifies
automatically whoever reads it when it changes, without the need to subscribe/unsubscribe.
Compared to an RxJS Observable, the key difference is that a Signal always has a
current value synchronous and immediately available (read by calling it
as a function: count()), while an Observable represents one
event stream over time that may not have emitted anything yet.
import { signal, computed, effect } from '@angular/core';
const count = signal(0);
const doubled = computed(() => count() * 2); // si ricalcola automaticamente
effect(() => {
console.log('Il valore doppio è ora:', doubled()); // si riesegue ad ogni cambiamento
});
count.set(5); // stampa: Il valore doppio è ora: 10
count.update(v => v + 1); // stampa: Il valore doppio è ora: 12
In practice: Signals are ideal for local synchronous state of a component (counters, forms
state, derived data), while RxJS remains the right tool for complex asynchronous flows
(debounce on an input, retry on an HTTP call, combining multiple streams). Angular
modern makes them interoperate with toSignal() and toObservable(), so
the question is not "which one" but "which one for this specific case".
12. What is the difference between traditional @Input()/@Output() and the new signals-based input()/output()?
Traditional @Input()/@Output() decorators declare properties
normals (or EventEmitter) that Angular populates/observes via its mechanism
internal; the new functions input()/output() (Angular 17.1+)
they return a read-only Signal and a typed emitter respectively, integrating
natively with computed() and effect().
// Stile tradizionale
@Component({ selector: 'app-counter', standalone: true })
export class CounterComponentLegacy {
@Input() initialValue = 0;
@Output() valueChange = new EventEmitter();
}
// Stile moderno basato su Signals
@Component({ selector: 'app-counter', standalone: true })
export class CounterComponent {
initialValue = input(0); // Signal, in sola lettura
initialValueRequired = input.required(); // obbliga il chiamante a passarlo
valueChange = output(); // tipizzato, senza dover importare EventEmitter
// Con input() come Signal, puoi derivare stato reattivo direttamente:
doubledInitial = computed(() => this.initialValue() * 2);
}
The practical advantage of the new input()/output() is not only
syntactic: being Signal, they are automatically integrated into the Angular reactivity graph
(especially useful in zoneless applications), and input.required() moves an error
which was previously discovered at runtime in a check verifiable at compile time with lo
strict template checking.
Senior Level — Architecture, Change Detection, Rendering, Performance and Testing
At the Senior level, the interviewer is not looking for the right definition: he is looking for reasoning. The questions often do not have a single correct answer, but they evaluate whether you can make an argument informed trade-off.
13. How does Angular's Change Detection mechanism work and what is the difference between Default and OnPush strategy?
With the Default strategy, every time Angular executes a change loop detection (triggered by DOM events, timers, completed HTTP calls, via Zone.js), every tree component is checked to see if the template bindings are changed, regardless of where the event actually occurred.
With OnPush, Angular checks a component only when: (1) a
@Input()/signal input changes by reference (not by internal mutation
of the object), (2) a DOM event happens inside it, (3) an Observable to which it is
connected via | async outputs a new value, or (4) is marked
explicitly with markForCheck()/updating a Signal that reads.
@Component({
selector: 'app-product-card',
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
template: `{{ product().name }} — {{ product().price | currency }}
`,
})
export class ProductCardComponent {
product = input.required();
}
// ❌ Questo NON scatena il change detection su ProductCardComponent con OnPush,
// perché muta l'oggetto esistente invece di sostituirlo (stesso riferimento):
someProduct.price = 99;
// ✅ Questo sì, perché crea un nuovo riferimento:
this.products.update(list =>
list.map(p => p.id === someProduct.id ? { ...p, price: 99 } : p)
);
A Senior answer must explicitly mention the concept of equality for reference: this is the number one cause of the "I updated the data but the UI doesn't work" bug update" when switching to OnPush without adopting immutable patterns consistently throughout the application.
14. What is zoneless Angular and how does the change detection model change?
Historically Angular uses Zone.js to "monkey-patch" asynchronous APIs browser (events, timer, promise, fetch) and automatically know when it might be a change detection cycle is necessary. The zoneless model (stable since Angular 18+) removes this dependency entirely: Angular relies exclusively on Signal to know exactly what has changed, without having to check the entire tree "just in case" whenever something asynchronous happens in the background.
// main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { provideZonelessChangeDetection } from '@angular/core';
bootstrapApplication(AppComponent, {
providers: [provideZonelessChangeDetection()],
});
The practical advantages: smaller bundle (Zone.js weighs around 30KB), change detection
significantly more targeted (only components that depend on a changed Signal are
updated), and more predictable debugging because each UI update is traceable to a
explicit Signal change, not to a caught generic asynchronous event "a
umbrella". The downside: dated third-party libraries that expect Zone.js (some
versions of Material components, graphics libraries) may require a
NgZone.run() manual to continue working correctly in
zoneless.
15. How would you diagnose and resolve a performance issue due to excessive change detection in production?
The first step isn't guessing: it's measuring with Angular DevTools, tab
Profiler. By recording a problematic interaction (e.g. typing in a search field), the
Profiler displays a bar graph where each bar is a controlled component in that cycle
— if a component unrelated to the interaction appears repeatedly, it is the missing signal
OnPush or that there is a poorly designed reactive pattern.
The typical resolution path, in order of impact:
- Apply
ChangeDetectionStrategy.OnPushto the most expensive "leaf" components to render, ensuring that data flows immutably. - Replace direct array/object mutations with immutable patterns (spread,
map/filter) or switch to Signal, which make equality by reference automatic and correct. - Add correct
track(the unique id, not index) in the@forblocks to prevent Angular from destroying and recreating DOM nodes unnecessarily when a list is reordered. - Evaluate the adoption of zoneless to eliminate change detection cycles triggered by asynchronous events not related to the visible UI.
An effective Senior answer always mentions Angular DevTools Profiler first step: Optimizing by feel without real profiling data is a common mistake even at Senior level, and whoever conducts the interview notices this immediately.
16. Which design patterns do you most often apply in an Angular enterprise architecture?
| Pattern | Application in Angular |
|---|---|
| Facade | A service that hides the complexity of multiple underlying services/stores behind a simple API for components (e.g. CartFacade which orchestrates CartService, PricingService, InventoryService). |
| Repository | A service dedicated to data access (HTTP, local cache) that isolates components from the implementation details of the data source. |
| Strategy | Injection of interchangeable implementations via InjectionToken, useful for behaviors that vary by environment or configuration (e.g. payment strategies different). |
| Smart/Dumb Component | Separation between "container" components (manage state and side-effects) and "presentational" components (receive data via input, emit events via output, without direct dependencies on services). |
| Adapter | Isolate the shape of external data (third-party APIs) behind a mapping to the application's internal models, so a change in the external API impacts only one point of the code. |
A Senior answer clearly distinguishes when each pattern adds value and when instead it is overengineering: introduce a Facade for a single simple service, for example example, it adds indirection with no real benefits.
17. How do you structure testing of a complex Angular application, and what trade-offs do you consider?
I follow the test pyramid: many fast and isolated unit tests (service, pipe,
pure logic extracted from components), a moderate number of integration tests (components
with TestBed, verifying real interaction with services), and a few E2E tests
targeted on the flows that are truly critical for the business (login, checkout), not on every single one
page.
// Unit test: logica pura, veloce, nessuna dipendenza da Angular
describe('calculateDiscount', () => {
it('applica correttamente uno sconto percentuale', () => {
expect(calculateDiscount(100, 20)).toBe(80);
});
});
// Integration test: componente reale con TestBed, verifica il comportamento visibile
describe('ProductCardComponent', () => {
it('emette addToCart quando si clicca il pulsante', () => {
const fixture = TestBed.createComponent(ProductCardComponent);
fixture.componentRef.setInput('product', mockProduct);
fixture.detectChanges();
let emitted: Product | undefined;
fixture.componentInstance.addToCart.subscribe(p => (emitted = p));
fixture.debugElement.query(By.css('[data-testid="add-btn"]')).nativeElement.click();
expect(emitted).toEqual(mockProduct);
});
});
The main trade-off that I always discuss in conversations: E2Es give maximum trust real (they test the app as a user would use it) but they are slow and more fragile; unit tests they are very fast but do not catch integration problems. The right relationship is not fixed, depends on the criticality of the domain — one payment flow deserves more E2E coverage than one static information page.
18. What is tree-shaking and how do architectural choices affect it?
tree-shaking is the process by which the bundler (esbuild in Angular 17+/20+) eliminates code that was exported but never actually imported from the final bundle or used, reducing the size of the JavaScript downloaded by the browser.
The architectural choices influence it in a concrete way: the standalones
components with explicit imports make dependencies static and
analyzable, improving tree-shaking compared to the old NgModule with
declarations "umbrella" statements that often mattered more than necessary. Even the
barrel file (index.ts which re-export an entire module) can
worsen tree-shaking if the bundler cannot statically determine which exports
are actually used, leading to dead code being included in the final bundle.
// ❌ Import "largo": può impedire il tree-shaking se lodash non è in formato ESM puro
import _ from 'lodash';
const chunks = _.chunk(array, 3);
// ✅ Import mirato: il bundler include SOLO la funzione realmente usata
import chunk from 'lodash/chunk';
const chunks = chunk(array, 3);
19. How would you implement client-side caching to reduce redundant HTTP calls in a scalable way?
The most elegant solution in modern Angular is a HttpInterceptor which
intercepts GET requests and returns a cached response when available and not
expired, specifically invalidating the cache when the underlying data changes.
@Injectable()
export class CacheInterceptor implements HttpInterceptor {
private cache = new Map; expiry: number }>();
intercept(req: HttpRequest, next: HttpHandler): Observable> {
if (req.method !== 'GET') return next.handle(req);
const cached = this.cache.get(req.urlWithParams);
if (cached && cached.expiry > Date.now()) {
return of(cached.response.clone());
}
return next.handle(req).pipe(
tap(event => {
if (event instanceof HttpResponse) {
this.cache.set(req.urlWithParams, {
response: event,
expiry: Date.now() + 60_000, // 60 secondi
});
}
}),
);
}
}
The point that distinguishes a Senior answer: knowing what to shit and what not to . Catalog data rarely changes and lends itself well to caching; specific data of the authenticated user require a cache key that includes the user's identity (or must be excluded altogether), otherwise you risk showing one user's data to another — a security error, not just a performance one.
Expert / Lead Level — Enterprise Architectures, Scalability, Microfrontend, SSR and Security
Expert/Lead questions rarely have a unique "right" answer. They evaluate the ability to argue a trade-off, defend a decision with hard data, and recognize when the "more sophisticated" solution is actually the wrong one for the context.
20. When does it make sense to adopt a microfrontend architecture versus a modular Angular monolith, and what are the real trade-offs?
Microfrontends make sense when there is a real organizational problem, not just technical: multiple teams that need to deploy independently, stacks or releases Angular different between parts of the application, or the need to completely isolate the release cycle of a critical section from the rest.
| Appearance | Modular Monolith | Microfrontend |
|---|---|---|
| Operational complexity | Low — one build, one deploy | High — orchestration, versioning, team contracts |
| Independent deployments | No | Yes, per team/section |
| Duplication of runtime dependencies | None | Concrete risk (multiple copies of Angular/RxJS) if not managed with module federation |
| UX consistency between sections | Natural | Requires explicit governance (shared design system) |
| Onboarding new developers | Simpler, single repo/architecture | More complex, need to understand the boundaries between applications |
My position in an interview: a well-structured modular monolith with feature boundaries clear (lazy-loaded, with explicit interfaces between modules) solves most of the problems that teams think they need to solve with microfrontends, with a fraction of the operational complexity. Microfrontends are an organizational tool for scale issues of teams, not a "better architecture" in the abstract — and should be adopted only when the cost of coordination between teams already exceeds the cost of additional technical complexity.
21. How would you design the SSR/hydration strategy for an enterprise Angular application with stringent SEO requirements?
I would start from Angular Universal with provideClientHydration() for
the complete hydration, evaluating incremental hydration
(withIncrementalHydration()) for unnecessary page-heavy sections
to the first render (graphics, rich-text editors, maps), combined with @defer for
postpone their loading until they enter the viewport.
// app.config.server.ts
import { provideServerRendering } from '@angular/platform-server';
import { provideClientHydration, withIncrementalHydration } from '@angular/platform-browser';
export const serverConfig = [
provideServerRendering(),
provideClientHydration(withIncrementalHydration()),
];
<!-- Il componente si idrata solo quando entra in viewport, non al bootstrap iniziale -->
@defer (on viewport) {
<app-heavy-dashboard-chart [data]="chartData()" />
} @placeholder {
<div class="chart-skeleton"></div>
}
For stringent SEO requirements, the critical architectural part is not just rendering:
you need a centralized SeoService that dynamically updates title, meta
description, canonical and JSON-LD during server-side rendering (not after, when
is too late for crawlers that don't run JavaScript), plus an explicit strategy for
manage code that accesses browser-only APIs (window, document)
behind a isPlatformBrowser() control, to avoid crashes during rendering
server side.
22. What security measures are essential in an Angular enterprise application?
- trusted — never on invalid user input.
- CSRF (Cross-Site Request Forgery):
HttpClientXsrfModuleautomatically handles the double-submit-cookie pattern, but only if the backend correctly sets the XSRF-TOKEN cookie. - Content Security Policy (CSP): Server-side configured header that limits the sources from which scripts/styles can be loaded, mitigating the impact of even successful XSS.
- JWT token management: short-lived access token, token refresh managed on the server side (never readable by JavaScript if possible, via httpOnly cookie), and real invalidation on logout.
- Client-side validation as UX, not security: any client-side validation must always be replicated on the server side — a client can be manipulated with development tools.
An Expert/Lead response must explicitly point out that client-side security it's a defense in depth, not the only line of defense: who thinks that validate/ protecting only Angular side is enough has a serious conceptual gap for this level.
23. How would you manage versioning and incremental migration of a legacy Angular codebase to standalone/signals on a team of 20+ developers?
Not with a "big bang rewrite" — too risky on a team that size. The strategy
which I adopt is the incremental migration by feature boundaries: Angular
supports coexistence of NgModule and standalone components in the same
application, so you can migrate one module at a time, verifying that each step is
independently deployable in production.
- Automate the first step with the official
schematicng generate @angular/core:standalone, which automatically converts components/directives/pipes. - Establish a clear boundary: new features are written only in standalone immediately, to prevent technical debt from continuing to grow during migration.
- Migrate legacy modules in order of increasing "radius of impact" — isolated, low-traffic features first, then progressively the core of the application.
- Introduce the Signals in parallel but separately from the migration to standalone: they are two orthogonal axes, there is no need to do them together and mixing them increases the risk per single change.
The point a Lead interviewer looks for: the ability to sequence risk, not just to learn about migration tools. Clearly communicate to the team why you are migrating gradually (and not all at once) is as much a part of the job as writing the code.
24. What criteria do you use to decide between NgRx, a simple custom Signal Store, or no state management library?
| Criterion | No library (Signal/services) | Light custom Signal Store | NgRx |
|---|---|---|---|
| Domain complexity | Low/medium | Medium | High, with many interconnected actions |
| Need for time-travel debugging | No | No | Yes |
| Team accustomed to Redux patterns | Not required | Not required | Advantage if already present |
| Acceptable boilerplate | Minimal | Moderate | Significant, mitigated by official schematics |
| Isolated state testability | Good | Excellent | Excellent, with established patterns |
My decision-making criterion in an interview: I always start from the simplest option (Signal in
a service providedIn: 'root') and I evolve it only when concrete symptoms emerge
— duplicate state logic between features, real need for advanced debugging, or a team that
already works well with Redux patterns on other projects. Introduce NgRx "by default" on each
new project is a choice that tends to slow down initial development without benefits
proportionate until the actual complexity justifies it.
25. How would you balance development speed, performance and maintainability in an architectural decision with stringent deadlines?
A concrete example of a real trade-off: a team needs to deliver a dashboard with graphs interactive in two weeks. The best performing chart library requires three days of advanced integration and configuration; a simpler library, less optimized but with excellent documentation, takes half a day.
The decision I would argue: use the simple library now, but isolate it behind an explicit internal interface/adapter (don't call it directly from each component), like this that if in the future real (not hypothetical) performance metrics show that the more sophisticated library, the substitution affects only one point of the code instead of being one widespread rewriting.
The general principle that I always communicate in conversations: speed of delivery and architectural quality are not always in conflict — often the real conflict is between speed of delivery and premature irreversible choices. Invest time to maintain reversible a decision (via a minimum abstraction layer, not an excessive one) allows to meet the deadline without compromising future maintainability. Decisions indeed expensive to change later (database schema, public API contracts, choice of framework) deserve more analysis time; easily reversible decisions (which library of graphic designers) don't deserve it, and insisting on "getting it right straight away" is often time wasted compared to the deadline.
How to Best Prepare for an Angular Interview
- Don't just memorize definitions: For each concept, ask yourself "what real bug does knowing this prevent?" — is exactly the kind of answer that distinguishes a solid candidate.
- Practice with real code, not just theory: build a small project that uses Signal, OnPush, lazy loading, and at least one test — practical knowledge always emerges in follow-up questions.
- Prepare concrete examples from your work: for Senior/Expert questions, having a real example (even a simplified one) of a trade-off you have faced is worth more than a flawless theoretical answer.
- Study common mistakes, not just best practices: knowing what goes wrong when you misapply a pattern (e.g. OnPush with direct mutations) demonstrates deeper understanding than just citing the rule.
- Stay updated on recent releases: Signal, standalone components, zoneless and the new control flow (
@if/@for) are now the expected standard in an Angular interview in 2026, not knowledge optional.
Conclusion
These 25 questions cover the natural growth path of a Frontend/Angular Developer: from the foundations of HTML/CSS/JavaScript/TypeScript, through RxJS and state management, up to the architectural decisions that a Lead must be able to motivate in front of a team or individual stakeholders. Whether you are preparing for an interview or conducting one, the common thread is always the same: understanding why a technical choice is the right one in a context specific, do not repeat a rule from memory.
If you are preparing for a Senior or Expert interview, the most concrete advice is this: choose three or four real architectural decisions you've made (even imperfect ones, even in a project personal) and be prepared to tell them in detail — what you chose, what alternatives you have discarded and why, what would you do differently today. It's the kind of answer that none textbook definition can replace.