Type Annotations
Learn how to use type annotations to guide TypeScript when inference isn't enough so your code stays safe, predictable, and intentional.
TypeScript’s inference is smart—but it’s not a mind reader. It knows let score = 100
is a number. Great. But what if there’s no value yet? What if it changes—or never shows up at all?
That’s when we stop relying on inference and start being intentional. We tell TypeScript what the type should be. This is the moment where inference ends and type annotations begin.
How to write a type annotation
Here’s the syntax. It’s simple but powerful:
let username: string = "Ada";let score: number = 42;let isAdmin: boolean = true;console.log(`Username: ${username}, Score: ${score}, Admin: ${isAdmin}`);
Explanation:
Line 1:
username
is explicitly typed as astring
.Line 2:
score
is anumber
.Line 3:
isAdmin
is aboolean
.
Type annotations don’t change how the code runs. They change how TypeScript thinks about the values. This is about telling the compiler your intent and ensuring it is accurately enforced.
Why annotate at all?
Because inference doesn’t always work. And when it fails, TypeScript falls back to its weakest fallback: any
.
{"compilerOptions": {"target": "ES2020","module": "commonjs","strict": true}}
Here’s how we fix it:
{"compilerOptions": {"target": "ES2020","module": "commonjs","strict": true}}
Modeling absence with union types: null
and undefined
Not every variable starts with a value. Sometimes, it's empty on purpose—because we're waiting for something to happen, or because the value might never come at all. This is where type annotations give us room to be flexible without giving up safety. When a variable might be one of multiple types—like a string or null
—we can say so explicitly:
{"compilerOptions": {"target": "ES2020","module": "commonjs","strict": true}}
Here, we’re saying: “error
might start as null
, but if something goes wrong, it’ll become a string
.”
That’s a special case of the more general union types, written with |
. We’ll dig deeper ...