Basic types and inference
The primitives, arrays, unions and the inference rules that mean you annotate far less than you expect.
Primitives and inference
let title: string = 'Notes'; // explicit annotation
let count = 0; // inferred as number
let tags = ['a', 'b']; // inferred as string[]
let maybe: string | null = null; // union with null
const mode = 'dark'; // type is the literal "dark"
let loose = 'dark'; // type is string, because let can change
function add(a: number, b: number): number {
return a + b;
}
const double = (n: number) => n * 2; // return type inferred from the bodyTypeScript infers types from initial values, so most variables need no annotation. Annotate the boundaries - parameters, exported return types and empty containers - and let inference handle the interior of a function.
anyswitches checking off for a value;unknownaccepts anything but forces you to narrow before use.voidmarks a function whose return value is ignored;nevermarks one that cannot return at all.readonly string[]prevents mutation, andas constfreezes a value into its literal types.
Unions and narrowing
type Result =
| { ok: true; value: string }
| { ok: false; error: string };
function render(result: Result) {
if (result.ok) {
return result.value; // narrowed to the success branch
}
return result.error; // narrowed to the failure branch
}
// truthiness is not the same as presence
function size(value: string | string[] | undefined) {
if (value === undefined) return 0; // '' and [] are truthy, undefined is not
return value.length;
}| Type | Example | Use it for |
|---|---|---|
| string / number / boolean | 'a', 1, true | Everyday values |
| null / undefined | explicit absence | Optional data, with strict null checks enabled |
| array / tuple | string[], [number, string] | Lists and fixed-length combinations |
| union | 'a' | 'b' | A closed set of allowed values |
| literal | 'dark' | Configuration values that must be exact |
| unknown | parsed JSON | Anything you have not validated yet |
⚠️
Turn on
strict in the compiler options from day one. Adding strict null checks to an existing codebase later turns hundreds of latent null bugs into a wall of errors - better to pay that cost at the start than to keep reaching for any.FAQ
Is it worth annotating everything?
No. Annotations that duplicate inference add noise and drift out of date. Annotate the public surface: function parameters, exported return types and empty arrays.
What is the difference between any and unknown?
any disables checking for that value, while unknown allows any value in but requires you to prove its type before use. Reach for unknown at boundaries.Related
Interfaces, type aliases and generics tsconfig and tooling
Last refreshed 2026-09-18.