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 should earn its place. There are two situations where it clearly does.
Annotations use a colon after the variable name:
let score: number
score = 42
This annotation helps because there is no initializer to inspect. Without it, TypeScript has nothing to infer from at the declaration. With it, the contract is set immediately: TypeScript now rejects a string assignment to score.
score = 'forty-two'
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 differently per case, or a try/catch that assigns inside the try.
Widening an initial value deliberately
An annotation can also widen an initial value deliberately:
let status: 'draft' | 'published' = 'draft'
status = 'published'
Without the union, TypeScript would infer a mutable string variable. That inference is reasonable — a let initialized with 'draft' usually wants other strings later — but it accepts any string, including 'pubished' with a typo. The annotation preserves the two allowed values as a contract, so the typo fails:
Type '"pubished"' is not assignable to type '"draft" | "published"'.
Here the annotation is not restating the initializer. It records an intention inference cannot guess: this variable cycles between exactly two states.
Annotations that hurt
Skip annotations that merely repeat what the initializer already says:
const port: number = 3000
The : number adds no information and one more thing to update. Worse, it can discard precision — inference would give the const the literal type 3000.
And do not use annotations to force a value into an incompatible type. If TypeScript rejects the initializer, the error is information about your data. Fix the data or the contract instead of adding an assertion. An annotation that fights the compiler is a bug report you are refusing to read.
Exercise: declare let result without an initializer, then assign values of different kinds. Compare that with let result: string | number and inspect which assignments are rejected.
Lesson completed