trackBy tells Angular how to uniquely identify items in an *ngFor list so it can reuse existing DOM nodes and component instances when the array changes. Without it (or with a bad trackBy), Angular may destroy and recreate rows unnecessarily, which hurts performance and can reset UI state like input values, focus, and child component state. TrackBy preserves DOM state and performance; test with reorders and large lists.
Why is trackBy important in *ngFor, and what breaks without it?
Use guided tracks for structured prep, then practice company-specific question sets when you want targeted interview coverage.
Overview*ngFor maintains a list of embedded views (DOM + directives + child components). trackBy defines each item's identity so Angular can do efficient diffs: move/update existing views instead of destroy/recreate them.
<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.