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 body

TypeScript 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.

  • any switches checking off for a value; unknown accepts anything but forces you to narrow before use.
  • void marks a function whose return value is ignored; never marks one that cannot return at all.
  • readonly string[] prevents mutation, and as const freezes 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;
}
TypeExampleUse it for
string / number / boolean'a', 1, trueEveryday values
null / undefinedexplicit absenceOptional data, with strict null checks enabled
array / tuplestring[], [number, string]Lists and fixed-length combinations
union'a' | 'b'A closed set of allowed values
literal'dark'Configuration values that must be exact
unknownparsed JSONAnything 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.

Interfaces, type aliases and generics tsconfig and tooling

Last refreshed 2026-09-18.