Control Flow Blocks and the Mandatory track In short: was never really part of Angular's template language. Moving it in fixed four problems at once — and made one small keyword, , compulsory for a very good reason. The new way of writing it Shorter — but what it made possible is the interesting part. What the change bought No more . Control flow is part of the language now, so there is nothing to import. One of the most common Angular puzzles — "why is my doing nothing?" caused by a forgotten import — simply cannot happen with . Types work properly inside the block. used a special helper to tell the compiler what type a value had inside the template. It worked, but it was fragile. The compiler understands directly: Two conditions on one element. This was never allowed: Now you just nest, and no extra wrapper element is created: The compiler can optimise it. Because control flow is a language construct rather than a directive, the compiler generates the update code directly instead of routing through general-purpose directive machinery. Angular's documentation describes as letting Angular "optimize performance by executing the minimum necessary DOM operations when the data changes." You will not notice this on ten items. It matters on large lists that update often. , and why it helps had no empty case. You wrote a separate next to it — so the condition was written twice and could drift apart. One block, no repeated condition. The built-in variables is the common one. The rest save a lot of arithmetic: Available: , , , , , . You can rename any of them with , which you need when loops sit inside each other. — the part that needs care was optional in , and most projects skipped it. is required in . That is on purpose: a small annoyance that prevents a large group of bugs. What does When your list changes, Angular has to work out which page elements to keep, move, create, and remove. gives it a way to recognise each item. Without a stable way to recognise items, Angular compares the objects themselves. Anything that creates new objects — a , a fresh server reply, an immutable update — makes every item look brand new. So every row gets destroyed and rebuilt. That means: lost focus in a text box, lost scroll position, restarted animations, restarted child components, and a full page rebuild instead of a small patch. Choosing what to track That last case is where things go wrong. If you track by position and the list gets sorted, filtered, or has an item removed from the middle, Angular matches position 0 to position 0 — but they are now different items. The bug this causes, worth learning by heart Three tasks: Buy milk , Call Ana , Book flight . The user edits "Book flight" to "Book flights to Porto" but has not clicked away yet — so the text box holds unsaved text. Then they delete "Call Ana". The list becomes . Angular tracks by position. Position 1 used to be "Call Ana" and is now "Book flight" — same key, so reuse that element . It updates the binding, but the text box's live value is not reset, because the element was never rebuilt. Result: the row now says "Book flight", the text box still contains "Book flights to Porto", and the delete button points at a different task. The next time the user clicks away, the wrong text is saved to the wrong record. No error. No warning. Data damage from one template keyword. With , Angular knows Call Ana is gone and Book flight is the same item it always was. It removes one element and leaves the other alone. Duplicates Since v18 Angular warns when a expression gives the same key twice in one list, because it then cannot tell those two items apart. If you see , your key is not unique. Combined keys work: Two differences from : it compares strictly, and is genuinely optional — with no default and no match, nothing shows. v21.1 added shared cases, which never had: And v21.2 added a completeness check, which is very useful with a fixed set of values: tells the compiler you believe you have covered every case. Add a fourth value later and forget to handle it, and you get a build error instead of a blank space on screen. "But isn't track just trackBy with a new name?" Same idea, yes. Very different consequences, because one was optional and one is not. was easy to skip, and skipping it had no visible symptom at small scale. is required in — omitting it is a compile error. Angular's documentation is explicit that this is deliberate, given the performance cost of getting it wrong. And is not mainly about speed. That is the second half of the misunderstanding. Speed is the visible benefit. Recognising items is the real one. Get it wrong and you do not get a slow list — you get a wrong list, as the task example above shows. The architect's view What actually controls is whether Angular reuses a DOM element or builds a new one. That single decision has three separate consequences, and only one of them is speed. 1. Correctness. A reused element keeps live DOM state that bindings do…