Everyday Types

Each type describes a set of the JavaScript values from JavaScript: number is every number, "GET" is one string, string | null is every string plus null. A small vocabulary covers most code.

Everyday TypeScript types
Type Example annotation Meaning
string, number, boolean, bigint, symbol let price: number = 9.5 Lowercase; no separate int or float
null, undefined let el: Element | null Separate types under strictNullChecks
Array string[] or Array<string> [string, number] is a tuple (Object Types)
Object type { x: number; y?: number } ? marks an optional property
Union number | string A value of either type
Literal "left" | "right" Exactly these values
Function (n: number) => string Parameters and result (More on Functions)
void function log(): void Returns no useful value
any let raw: any Opts out of checking
unknown let input: unknown Anything; must be narrowed before use
never function fail(): never No value: throws or cannot happen
Annotations and inference

An annotation follows the name after a colon. Variables rarely need one: TypeScript infers the type from the initializer and keeps it, so a let that starts as a number stays a number. Annotate function parameters, and the return type of exported functions to document intent. Callbacks need nothing: contextual typing gives tag in tags.forEach((tag) => ...) the array's element type.

Inferred variables, annotated parameters, contextually typed callbacksJavaScript
let title: string = "TypeScript";   // annotation (redundant here)
let count = 42;                     // inferred: number
const tags = ["ts", "js"];          // inferred: string[]
function average(values: number[]): number {
  return values.reduce((sum, v) => sum + v, 0) / values.length;
}
tags.forEach((tag) => console.log(tag.toUpperCase()));  // tag: string
count = "many";
average(tags);
title.toUppercase();
Output
everyday.ts(10,1): error TS2322: Type 'string' is not assignable to type 'number'.
everyday.ts(11,9): error TS2345: Argument of type 'string[]' is not assignable to parameter of
  type 'number[]'.
  Type 'string' is not assignable to type 'number'.
everyday.ts(12,7): error TS2551: Property 'toUppercase' does not exist on type 'string'. Did
  you mean 'toUpperCase'?
Object types, unions, aliases and interfaces

An object type lists property names and their types; ? makes a property optional (its type gains undefined) and readonly forbids reassignment. A union A | B accepts either member, but you may only use operations valid for every member, so id.toFixed() fails when id might be a string. Give any type a reusable name with a type alias, or name an object shape with an interface.

An interface, a union alias, optional and readonly propertiesTypeScript
type Id = number | string;                // type alias for a union
interface User {
  id: Id;
  name: string;
  email?: string;                         // optional: string | undefined
  readonly createdAt: Date;
}
interface User { roles: string[] }        // interfaces merge; aliases cannot
function label(user: User): string {
  const mail = user.email.toLowerCase();
  return `${user.name} #${user.id.toFixed(0)}`;
}
const ada: User = { id: 1, name: "Ada", createdAt: new Date(), roles: [], age: 36 };
ada.createdAt = new Date();
type Id = bigint;
Output
objects.ts(1,6): error TS2300: Duplicate identifier 'Id'.
objects.ts(12,16): error TS18048: 'user.email' is possibly 'undefined'.
objects.ts(13,35): error TS2339: Property 'toFixed' does not exist on type 'Id'.
  Property 'toFixed' does not exist on type 'string'.
objects.ts(16,75): error TS2353: Object literal may only specify known properties, and 'age'
  does not exist in type
'User'.
objects.ts(17,5): error TS2540: Cannot assign to 'createdAt' because it is a read-only
  property.
objects.ts(18,6): error TS2300: Duplicate identifier 'Id'.

TypeScript is structurally typed: any object with the right properties is a User. The age error is an excess property check, applied only to object literals written in place, where an extra property is usually a typo. Narrowing (Narrowing) fixes the email and id errors.

Interfaces versus type aliases
interface type alias
Names Object shapes only Any type: unions, tuples, primitives
Extending interface B extends A {} Intersection type B = A & {}
Re-opening Declarations merge Duplicate identifier error

The handbook's rule of thumb: use interface until you need a feature only type offers. Merging is how libraries let you add properties to globals such as Window (Reference Topics).

Literal types and as const

A const holding "GET" has the literal type "GET", but a let or an object property is widened to string because it could change later. To keep an object's literal types, add as const, which also makes it readonly.

Widening, literal unions, as const, assertions, any and unknownTypeScript
type Method = "GET" | "POST";
declare function send(url: string, method: Method): void;
let loose = "GET";                      // widened to string
const req = { url: "/api", method: "GET" };
const frozen = { url: "/api", method: "GET" } as const;
send(req.url, loose);
send(req.url, req.method);
send(frozen.url, frozen.method);        // OK: method is "GET"
const canvas = document.getElementById("chart") as HTMLCanvasElement;
const input = document.querySelector("input")!;   // trust me: not null
const n = "42" as number;
const data: unknown = JSON.parse('{"x":1}');
data.x;
const risky: any = JSON.parse('{"x":1}');
risky.y.z();                            // no error, crashes at runtime
Output
literals.ts(8,15): error TS2345: Argument of type 'string' is not assignable to parameter of
  type 'Method'.
literals.ts(9,15): error TS2345: Argument of type 'string' is not assignable to parameter of
  type 'Method'.
literals.ts(14,11): error TS2352: Conversion of type 'string' to type 'number' may be a
  mistake because neither type
sufficiently overlaps with the other. If this was intentional, convert the expression to
  'unknown' first.
literals.ts(17,1): error TS18046: 'data' is of type 'unknown'.
Assertions, any and unknown

A type assertion expr as T tells the checker you know better. getElementById returns HTMLElement | null, and only you know that #chart is a canvas. Assertions are erased, so nothing checks them at runtime, and TypeScript allows them only toward a more or less specific type: "42" as number is rejected. The postfix ! is a shorthand assertion that removes null and undefined. Both are promises you must keep.

any switches checking off in both directions and spreads silently: JSON.parse returns any, so risky.y.z() compiles and throws a TypeError when it runs. Annotate such values as unknown instead; the checker then refuses every operation until a typeof, in or custom guard proves what the value is.