"You don't write types. You write code, and TypeScript writes the types."
The inference engine — your most powerful collaborator
TypeScript doesn't only type-check what you annotate. It runs a sophisticated inference engine that reads your code and figures out types from context — from values, from operations, from function calls, from control flow. The previous lesson said "annotate at the doorways." This lesson explains why: because the inference engine handles the hallways for you.
Open any TypeScript file you've written and hover over variables. The editor displays the inferred type for every binding. Most of the time, there's an annotation that would say the same thing — and that's exactly the annotation you can delete.
Where inference happens
Variable initialization: const x = 1 infers x: 1. let y = 'hi' infers y: string. (Yes — const narrows to the literal type, let widens. The const vs let section below unpacks that.)
Function return types: a function with no return-type annotation has its return type inferred from the return statements in the body. function double(n: number) { return n * 2 } has return type number automatically.
Generic call sites: arr.map(x => x.length) on a string[] infers x: string automatically. You never had to write x: string — the call site told the compiler.
Object literals: { name: 'Pippa', age: 21 } infers { name: string; age: number }. The fields are inferred individually.
Destructuring: const { name } = user gets the right type for name automatically by reading user's type.
const vs let — narrow vs wide inference
TypeScript widens the inferred type based on whether the binding can be reassigned:
const status = 'idle'→status: 'idle'(the literal type, becauseconstcan never change).let status = 'idle'→status: string(widened, becauseletcould be reassigned to any string).
This widening rule is why people reach for as const later — to force the narrow inference even on mutable bindings or array literals. We cover as const in detail in the Arrays & Tuples track.
Inference inside control flow
Inference isn't just about initialization. It tracks types through if branches, switch cases, early returns, and even short-circuit expressions. This is called control flow analysis, and the Narrowing track covers it in depth.
A tiny preview: inside if (typeof x === 'string') { /* here x is narrowed to string */ }, the compiler knows that x has type string only inside that branch. Outside the branch, it goes back to whatever wider type it had. The compiler builds this narrowing-graph automatically — you don't need to write a single annotation to get it.