trackBy is Angular list identity, not just a micro-optimization. It prevents production bugs like lost focus, reset inputs, and child state churn by letting Angular reuse the right DOM rows when arrays refresh or reorder.
Why is trackBy important in *ngFor, and what breaks without it?
Identity-first view*ngFor identity is not just about shaving milliseconds off a list. trackBy tells Angular which row is the same logical item, so the framework can reuse DOM nodes and child components instead of destroying them. That is why bad identity causes real production bugs like lost focus, reset inputs, and row state sticking to the wrong item.
<ul>
<li *ngFor="let user of users; trackBy: trackByUserId">
<app-user-row [user]="user"></app-user-row>
</li>
</ul>
type User = { id: string; name: string };
export class UsersComponent {
users: User[] = [
{ id: 'u1', name: 'Ada' },
{ id: 'u2', name: 'Linus' }
];
trackByUserId(index: number, user: User): string {
return user.id;
}
// Common real-world cause of problems: new object references on refresh
refreshFromApiLike(): void {
// Same ids, but NEW objects => without trackBy, Angular treats them as new items
this.users = this.users.map(u => ({ ...u }));
}
}
What trackBy controls | What Angular can do | Why it matters |
|---|---|---|
Item identity | Match old items to new items across changes | Prevents treating “same logical item” as a brand-new row |
DOM reuse | Reuse existing DOM nodes and directives | Avoids heavy DOM churn and layout/repaint work |
Component instance reuse | Keep child component instances for the same logical item | Preserves child state and avoids repeated ngOnInit/ngOnDestroy |
Row movement | Move existing views when sorting/reordering | Keeps UI stable instead of flickering or re-initializing |
Without trackBy (or with unstable identity) | What breaks / degrades | Typical symptom |
|---|---|---|
Array recreated with new object references | Rows are destroyed + recreated even if ids are the same | Inputs reset, focus/caret jumps, child state resets |
Expensive child rows (components, heavy templates) | More DOM work + more lifecycle work | Scroll jank, CPU spikes, visible flicker on updates |
UI state stored in the DOM (focus, selection, <input> value while typing) | DOM state is lost on re-create | User is typing and the caret jumps / typed text disappears |
3rd-party widgets inside rows (charts, editors) | Widgets get torn down and rebuilt | Re-initialization costs + lost internal widget state |
Bad trackBy (random, Date.now, returning a new object) | Identity changes every check => Angular rebuilds everything | Worst-case: every CD cycle behaves like a full rerender |
TrackBy choice | When it's correct | When it causes bugs |
|---|---|---|
Stable unique id (recommended) | Items have a real unique key (id, uuid, slug) | Rarely; only if the id is not actually unique/stable |
Composite key (e.g., | Multiple item types share the same id space | If any part of the composite can change over time |
Value itself (primitives) | List is strings/numbers and values are unique/stable | If values can repeat (duplicates) => identity collisions |
Index (avoid unless list is static) | List never reorders/inserts/removes, only updates values in-place | On insert/reorder, state sticks to the wrong row (wrong input value on wrong item) |
Extra note
Modern Angular also has @for with a track expression (same concept): @for (user of users; track user.id) { ... }. The principle is identical: stable identity => stable DOM + preserved state.
Practical scenario
A table with editable rows should preserve input focus when new data arrives from the server.
Common pitfalls
- Using index as trackBy, which breaks on reorders.
- Returning non-unique ids, causing UI glitches.
- Skipping trackBy and forcing full DOM re-creation.
TrackBy improves performance but requires stable ids. Test by reordering and verifying focus retention.
trackBy is important because it gives Angular a stable key per item so it can reuse and move existing rows instead of recreating them. Without it (especially when data refresh creates new object references), you pay in performance and you lose UI state (inputs, focus, child component state). Use a stable unique id; avoid index unless the list is truly static.
Use the relevant interview-question hub first, then move into a concrete study plan before targeted company sets.