Types and inference
Let TypeScript infer obvious types
Use inference for initialized local values and add annotations at boundaries where they make a contract clearer.
You do not need to annotate every variable. TypeScript looks at the initial value and works out the type on its own. This is inference, and good TypeScript code leans on it heavily.
Two variables, no annotations:
const course = 'TypeScript'
let status = 'draft'
Hover over course in your editor. The type is not string. It is 'TypeScript', the literal value itself. Because a const can never be reassigned, TypeScript knows the value will always be exactly that string, so it keeps the most precise type it can.
Now hover over status. The type is string. A let can be reassigned, so TypeScript widens the type to cover any string you might assign later. That is the right call: you declared it with let because you plan to change it.
Inference still protects you. Try to assign a number:
status = 42
error TS2322: Type 'number' is not assignable to type 'string'.
TypeScript inferred string from 'draft' and now holds you to it. Writing let status: string = 'draft' would give you exactly the same result, with more typing. The annotation repeats what the compiler already knows.
Inference follows expressions
Inference is not limited to literals. It flows through expressions:
const lessons = 42
const label = `${lessons} lessons`
lessons is the literal type 42. label is string, because a template literal always produces a string. TypeScript worked that out from the expression, not from an annotation.
The same happens with function calls, arithmetic, array methods and object literals. Most of the values inside a function body get their types this way.
Where annotations still belong
So when do you write a type? Where the contract is not obvious from the value:
- function parameters, because there is no initial value to look at
- exported functions and their return types, because other files depend on them
- empty arrays and objects, because
[]tells TypeScript nothing about the elements - values whose allowed type is wider than the initializer, like a
letthat will hold either a string or a number
My advice is to let local implementation details infer their types. Add annotations where they record a decision someone else needs to know about. The next lesson looks at those cases in detail.
Try this on your own: change const course to let course and hover over it again. Watch the type widen from 'TypeScript' to string.
Lesson completed