Types and inference
Add a type annotation when it helps
Write annotations after variable names and use them to express an intentional contract rather than restating an initializer.
Inference handles most variables, so an annotation has to earn its place. There are two situations where it clearly does. Let’s look at both.
An annotation goes after the variable name, with a colon:
let score: number
score = 42
This one helps because there is no initial value. Without the annotation, TypeScript has nothing to infer from at the declaration. With it, the contract is set right away, and a string assignment fails:
score = 'forty-two'
error TS2322: Type 'string' is not assignable to type 'number'.
Declare-then-assign shows up whenever the value comes from a branch. An if/else that computes the result in two different ways, or a try/catch that assigns inside the try. In all those cases, annotate the declaration.
Widen an initial value on purpose
The second case is when inference would pick a type that is too loose or too tight for what you mean:
let status: 'draft' | 'published' = 'draft'
status = 'published'
Without the annotation, TypeScript infers string for this let. Reasonable, but it accepts any string, including a typo. With the annotation, only two values are allowed, and the typo fails:
status = 'pubished'
error TS2820: Type '"pubished"' is not assignable to type '"draft" | "published"'. Did you mean '"published"'?
Notice the compiler even suggests the fix. Here the annotation is not repeating the initializer. It records an intention inference cannot guess: this variable moves between exactly two states, and nothing else.
Annotations that hurt
Skip annotations that only repeat what the initializer already says:
const port: number = 3000
The : number adds no information, and it is one more thing to update when the code changes. It can even lose precision. Inference would give this const the literal type 3000. The annotation widens it to any number.
The worst use of an annotation is to force a value into a type it does not have. If TypeScript rejects the initializer, that error is information about your data. Fix the data, or change the contract on purpose. An annotation that fights the compiler is a bug report you are refusing to read.
My rule is short. Annotate when there is no initializer, or when the intended type differs from what inference would pick. Otherwise, let TypeScript do the work.
Try this on your own: declare let result without an initializer or annotation, then assign values of different kinds and hover over it. Compare that with let result: string | number and see which assignments get rejected.
Lesson completed