Everyday Types

This section documents the current TypeScript release line as published at the official TypeScript documentation, which is the reference these pages are written and verified against. No specific patch version is pinned.

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production, since TypeScript iterates quickly.

This section’s bibliography lists the reference material consulted while preparing these pages.

TypeScript attaches a static type to every value JavaScript already has. This page is the working vocabulary — the handful of types that show up in almost every file. It follows the official Everyday Types handbook chapter, and pairs with the JavaScript-side Types, Values & Conversions page for how the underlying values behave at runtime.

Primitives

The three most common primitives are string, number, and boolean. Note the lowercase names — they are the TypeScript types, distinct from the wrapper objects below.

const name: string = "Ada";
const age: number = 36;          // one numeric type, no int/float split
const active: boolean = true;

bigint (arbitrary-precision integers) and symbol (unique keys) round out the set:

const big: bigint = 9007199254740993n;
const id: symbol = Symbol("id");

symbol has its own handbook page, Symbols, covering unique symbol and the well-known symbols.

null and undefined are both types and values. With strictNullChecks on (part of strict), they are only assignable where you name them explicitly:

let end: number | null = null;
end = 42;

function greet(x?: string) {   // x: string | undefined
  return x ?? "stranger";
}

Wrapper objects — avoid String, Number, Boolean

The capitalized String, Number, and Boolean types refer to the boxed wrapper objects, not the primitives. They are almost never what you want: a primitive is assignable to the wrapper type but not the other way round, and the ergonomics are worse.

let a: string = "hi";
let b: String = a;   // allowed, but pointless
a = b;               // Error: 'String' is not assignable to 'string'

Rule of thumb: lowercase string / number / boolean always. The uppercase forms exist only for rare interop edge cases.

Arrays and tuples

Write an array type as T[] or the equivalent Array<T>:

const xs: number[] = [1, 2, 3];
const ys: Array<string> = ["a", "b"];

readonly T[] (or ReadonlyArray<T>) drops the mutating members — push, splice, index assignment:

const frozen: readonly number[] = [1, 2, 3];
frozen.push(4);   // Error: 'push' does not exist on 'readonly number[]'

A tuple fixes the length and the type at each position:

const origin: [number, number] = [10, 20];
const entry: [string, number] = ["age", 36];

Arrays, readonly arrays, tuples, labelled elements, and variadic tuple types get the full treatment on Arrays and Tuples.

Object types

An object type lists its properties and their types. Written inline it is an object type literal, e.g. \{ x: number; y: number }:

function distance(p: { x: number; y: number }): number {
  return Math.hypot(p.x, p.y);
}
distance({ x: 3, y: 4 });   // 5

A ? marks a property optional (its type becomes T | undefined); readonly blocks reassignment after the object is built:

interface User {
  readonly id: string;
  name: string;
  nickname?: string;      // string | undefined
}

const u: User = { id: "u1", name: "Ada" };
u.name = "Ada L.";        // ok
u.id = "u2";              // Error: 'id' is a read-only property

A function-valued member can use method syntax or property syntax. Property syntax with an arrow type is checked more strictly for parameter variance and is the modern default:

interface Store {
  get(key: string): string;                    // method syntax
  set: (key: string, value: string) => void;   // property syntax
}

Interfaces versus type aliases, index signatures, and excess-property checks are covered on Objects and Interfaces.

any, unknown, void, never

any opts a value out of type checking entirely. It is contagious and should be rare — prefer a real type, or unknown.

let loose: any = 4;
loose.toUpperCase();   // no compile error, throws at runtime

unknown is the safe counterpart: anything is assignable to it, but you must narrow before you can use it.

function parse(json: string): unknown {
  return JSON.parse(json);
}

const data = parse('{"n":1}');
// data.n;                       // Error: 'data' is of type 'unknown'
if (data && typeof data === "object" && "n" in data) {
  console.log(data.n);          // ok after narrowing
}

void is the return type of a function that returns nothing meaningful:

function report(msg: string): void {
  console.log(msg);
}

never is the type with no values — the return type of a function that never finishes normally (it throws or loops forever), or a branch that cannot be reached. It powers exhaustiveness checks:

function fail(msg: string): never {
  throw new Error(msg);
}

type Shape = { kind: "circle"; r: number } | { kind: "square"; side: number };

function area(s: Shape): number {
  switch (s.kind) {
    case "circle": return Math.PI * s.r ** 2;
    case "square": return s.side ** 2;
    default: {
      const _exhaustive: never = s;   // errors here if a case is missed
      return _exhaustive;
    }
  }
}

Literal types and inference

A literal type is a single exact value — "GET", 42, true. Alone it is rarely written by hand; combined in a union it models a fixed set of options:

type Method = "GET" | "POST" | "PUT" | "DELETE";

function request(url: string, method: Method) { /* ... */ }
request("/api", "POST");     // ok
request("/api", "PATCH");    // Error: '"PATCH"' is not assignable to 'Method'

Widening

How TypeScript infers a literal depends on let versus const. A const binding to a literal keeps the literal type; a let binding widens to the base primitive:

const a = "GET";     // type "GET"
let b = "GET";        // type string  (widened)

The same widening happens to object properties, which is why handing an object literal to a Method parameter can fail:

const req = { url: "/api", method: "POST" };   // method: string
request(req.url, req.method);   // Error: string is not assignable to 'Method'

as const

as const freezes an expression to its narrowest, deeply readonly form — literal types preserved, arrays turned into readonly tuples:

const req = { url: "/api", method: "POST" } as const;
// { readonly url: "/api"; readonly method: "POST" }
request(req.url, req.method);   // ok now

const methods = ["GET", "POST"] as const;
// readonly ["GET", "POST"]
type M = (typeof methods)[number];   // "GET" | "POST"

A narrower alternative for one value is an explicit type annotation on the binding, or an annotation inside a destructuring pattern:

const req2: { url: string; method: Method } = { url: "/api", method: "POST" };

function handle({ method }: { method: Method }) { /* ... */ }

In a destructuring pattern the annotation goes on the whole pattern — \{ method }: \{ method: Method } — not \{ method: Method }, which would instead rename the binding to Method. The Objects, Properties & Destructuring page covers the runtime destructuring syntax; the complete set of inference and widening rules is in the handbook’s Variable Declarations chapter.

Modifiers such as readonly and as const, along with satisfies and the assertion forms (as, !), are detailed on Type Assertions and Modifiers.