Annotations & Inference
You can tell TypeScript the type of a value explicitly with an annotation, or let it work the type out from the code — that's inference. Good TypeScript uses both: annotations where they document intent, inference everywhere else.
Explicit annotations
An annotation is the : type you write after a name. It's a promise: from now on, only values of that type may live here.
let city: string = "Lisbon"; let population: number = 545_000; function area(width: number, height: number): number { return width * height; } console.log(city, population, area(3, 4));
Lisbon 545000 12
Inference
Writing : string next to a string literal is repetitive. When a variable is initialised, TypeScript infers its type from the value:
let city = "Lisbon"; city = 42;
error TS2322: Type 'number' is not assignable to type 'string'.
There's no annotation, yet city is a string — exactly as if you'd written one. Inference isn't "less typed"; it's the same type checking with less noise.
Inference also works for function return values, array elements and object properties:
function double(n: number) { return n * 2; } const result = double(21); const tags = ["new", "sale"]; const user = { name: "Ana", active: true }; console.log(typeof result, typeof tags[0], typeof user.active);
number string boolean
Hover over double in an editor and you'll see function double(n: number): number — the return type was inferred from the return statement.
let vs const inference
How specific the inferred type is depends on whether the variable can change:
let mutable = "draft"; const fixed = "draft"; // mutable is inferred as: string // fixed is inferred as: "draft" mutable = "published"; console.log(mutable, fixed);
published draft
A let can be reassigned later, so TypeScript widens it to the general type string. A const can never change, so its type is the exact literal type "draft". Literal types turn out to be extremely useful — lesson 11 is dedicated to them.
Where inference can't help
Inference needs something to infer from. Two situations where you must annotate:
Function parameters
A parameter has no value until someone calls the function, so TypeScript can't guess it. Without an annotation (and with strict mode on) you get an error:
function shout(text) { return text.toUpperCase(); }
error TS7006: Parameter 'text' implicitly has an 'any' type.
Variables declared before they're assigned
let status: string; if (Math.random() > 0.5) { status = "heads"; } else { status = "tails"; } console.log(status.length);
5
Without : string, status would start life with no information to infer from. TypeScript also checks that it's definitely assigned on every path before it's read.
When to annotate anyway
- Function parameters — always required.
- Exported function return types — optional, but writing them means a mistake inside the function is reported at the function, not at some distant caller.
- Empty arrays and objects —
const ids = []tells TypeScript nothing about future contents; writeconst ids: number[] = []. - Everything else — let inference do it.
const total: number = 0adds nothing thatconst total = 0doesn't already say.
Try it yourself
Each variable below is inferred. Predict what typeof will print before you run it: