Type operators build new types out of existing ones. They run in the compiler and leave nothing in the output. Conditional and mapped types, which are the other main way to compute types, have their own page.
Precedence
From tightest to loosest, type operators bind as follows. Parentheses group as in expressions.
| Level | Operators | Example |
|---|---|---|
| 1 | Array [] and indexed access T[K] | string[] |
| 2 | Prefix keyof, typeof, unique, readonly, infer | keyof T[] is keyof (T[]) |
| 3 | Intersection & | A | B & C is A | (B & C) |
| 4 | Union | | |
| 5 | Function types and conditional types | () => A | B returns the union |
A function type in a union therefore needs parentheses:
(() => string) | number. An array of a union does too:
(string | number)[].
Union types
A union A | B is a value of either type. Only the members common to every
part are available until the value is
narrowed.
function show(x: string | number) {
x.toString(); // fine: both have it
x.toUpperCase(); // error: only string has it
}
type Text = string | number | boolean;
type Same = string | never; // string
type All = string | unknown; // unknown
Calling a value of a union of function types requires an argument that fits every
member. The parameter types are intersected, so
((a: string) => void) | ((a: number) => void) needs an argument of type
string & number, which is never.
Intersection types
An intersection A & B is a value of both types. For object types it
combines the members. A property declared in both parts has the intersection of its
two types.
type Named = { name: string };
type Aged = { age: number };
type Person = Named & Aged; // { name: string; age: number }
type A = { k: string };
type B = { k: number };
type C = (A & B)["k"]; // never
type D = { kind: "a" } & { kind: "b" }; // never: the whole type reduces
type F = ((a: string) => string) & ((a: number) => number);
declare const f: F;
const n = f(1); // number: acts as overloads
neverWhen the conflict is on a discriminant, that is, a property with literal types, the
whole intersection reduces to never. Intersecting two incompatible
primitives, such as string & number, also gives never.
Compare interface
extension, which reports the conflict as an error.
keyof
keyof T is the union of the property names of T as literal
types
(Handbook:
Keyof).
| Type | keyof gives |
|---|---|
{ x: number; y: number } | "x" | "y" |
{ [k: string]: boolean } | string | number, since a number key is converted to a string |
{ [k: number]: boolean } | number |
A | B | The keys common to both |
A & B | The keys of either |
{} | never |
any, never | string | number | symbol |
unknown | never |
To get the names of an enum’s members, use keyof typeof E, since the enum
type itself has number methods.
typeof in a type
In a type position, typeof takes a value path and gives its type. Only
identifiers and property accesses are allowed, not expressions such as calls. In an
expression, typeof is still
JavaScript’s run-time operator.
const config = { port: 80, host: "localhost" };
type Config = typeof config; // { port: number; host: string }
type Port = typeof config.port; // number
function load(path: string) { return 1; }
type Loaded = ReturnType<typeof load>; // number
const names = ["a", "b"] as const;
type Name = (typeof names)[number]; // "a" | "b"
type Mod = typeof import("./module"); // the type of a whole module
typeof brings a value’s type into the type levelIndexed access types
T[K] is the type of property K of T.
K must be a type and must be a key of T
(Handbook:
Indexed Access Types).
type P = { a: number; b: string; c: boolean };
type A1 = P["a"]; // number
type A2 = P["a" | "b"]; // number | string
type A3 = P[keyof P]; // number | string | boolean
type A4 = P["z"]; // error: no such property
const key = "a";
type A5 = P[key]; // error: key is a value
type A6 = P[typeof key]; // number
type E1 = string[][number]; // string
type E2 = [string, number][0]; // string
type E3 = [string, number]["length"]; // 2
number as the index gives the element typeTemplate literal types
A template literal type (4.1) builds string types with the syntax of a template string.
Each placeholder must be a type assignable to string, number,
bigint, boolean, null or undefined
(Handbook:
Template Literal Types).
type Horizontal = "left" | "right";
type Vertical = "top" | "bottom";
type Corner = `${Vertical}-${Horizontal}`;
// "top-left" | "top-right" | "bottom-left" | "bottom-right"
type Version = `v${1 | 2}`; // "v1" | "v2"
type Flag = `${true}`; // "true"
type Id = `id-${number}`; // a pattern, not a list
const a: Id = "id-3"; // fine
const b: Id = "id-x"; // error
Unions multiply across placeholders. If the result would have 100,000 members or more,
the compiler reports that the type is too complex. A placeholder of
string or number stays as a pattern, which can be used as an
index signature key.
String manipulation
Four built-in types change case. The compiler itself implements them.
| Type | Example | Result |
|---|---|---|
Uppercase<S> | Uppercase<"ab"> | "AB" |
Lowercase<S> | Lowercase<"AB"> | "ab" |
Capitalize<S> | Capitalize<"ab"> | "Ab" |
Uncapitalize<S> | Uncapitalize<"AB"> | "aB" |
Inference from a pattern
With infer,
a pattern takes a string type apart. An infer followed by another
placeholder takes a single character. Since 4.8 an extends clause on the
inferred name can turn the text into a number or other literal.
type Trim<S extends string> = S extends ` ${infer R}` ? Trim<R> : S;
type T1 = Trim<" x">; // "x"
type Split<S extends string, D extends string> =
S extends `${infer A}${D}${infer B}` ? [A, ...Split<B, D>] : [S];
type T2 = Split<"a.b.c", ".">; // ["a", "b", "c"]
type AsNumber<S> = S extends `${infer N extends number}` ? N : never;
type T3 = AsNumber<"42">; // 42
Since 7.0, inference works on Unicode code points, so a character outside the Basic Multilingual Plane stays whole. Earlier versions split it into two UTF-16 code units. This can change the result of type-level string helpers that count characters.