Why is trackBy important in *ngFor, and what breaks without it?

HighIntermediateAngular
Quick Answer

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.

Answer

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.

HTML
<ul>
  <li *ngFor="let user of users; trackBy: trackByUserId">
    <app-user-row [user]="user"></app-user-row>
  </li>
</ul>
                  
TYPESCRIPT
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

trackBy lets Angular perform a stable, minimal update of the rendered list

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

Most “trackBy bugs” show up when you refresh data or rebuild arrays (common with immutability patterns)

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., ${type}:${id})

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)

Key rule: identity must represent the logical item, not its current position

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.
Trade-off or test tip
TrackBy improves performance but requires stable ids. Test by reordering and verifying focus retention.

Summary

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.

Similar questions
Guides
Preparing for interviews?

Use the relevant interview-question hub first, then move into a concrete study plan before targeted company sets.