A type describes a set of values. Types exist only at compile time and do not change
what the program does. The values themselves are those of
JavaScript. This page covers the basic kinds of type
and when one type can be used in place of another
(Handbook:
Everyday Types and Object
Types). The examples assume strict, the default since 6.0.
Annotations
A type follows a name after a colon. Annotations are optional wherever the compiler can
work the type out, and a name with no annotation and no way to infer it is an error
under noImplicitAny.
VariableDeclaration: Name [!] [: Type] [= Expression]
Type: PrimitiveName | TypeReference | ObjectType | ArrayType | TupleType
| LiteralType | FunctionType | ConstructorType
| UnionType | IntersectionType | KeyofType | TypeofType
| IndexedAccessType | ConditionalType | MappedType
| TemplateLiteralType | ( Type )
let count: number = 1;
let name = "Ada"; // string, inferred
function double(n: number): number { return n * 2; }
Primitive types
| Type | Values |
|---|---|
string | Strings |
number | Numbers, including NaN and Infinity |
bigint | BigInts. number and bigint do not mix |
boolean | true and false. It is the union true | false |
symbol | Symbols |
unique symbol | One specific symbol. Allowed only on a const or a readonly static property |
null, undefined | Each only its own value, under strictNullChecks |
The capitalised names String, Number, Boolean
and so on are the wrapper object types. A primitive can be assigned to one, but not the
other way round, so the lower-case names are the ones to use.
Without strictNullChecks, null and undefined can
be assigned to every type.
Special types
| Type | Meaning | Can be assigned to |
|---|---|---|
any | Turns checking off for the value. Every operation is allowed and gives any | Everything but never |
unknown | Any value, but no operation is allowed until it is narrowed | unknown and any only |
never | No values: a function that does not return, or a case that cannot happen | Everything |
void | The absence of a return value | void, unknown and any |
undefined | The value undefined | undefined, void, unknown and any |
null | The value null | null, unknown and any |
let a: any = 1;
a.foo.bar(); // no error, and a crash at run time
let u: unknown = "x";
u.toUpperCase(); // error
if (typeof u === "string") {
u.toUpperCase(); // fine
}
function fail(message: string): never {
throw new Error(message);
}
type Callback = () => void;
const cb: Callback = () => 42; // fine: the result is ignored
function nothing(): void {
return 42; // error
}
any switches checking off; unknown keeps it onvoid behaves differently depending on where it appears. A function
declared with void as its own return type may not return a value, but a
function type returning void accepts functions that return anything, and the
caller just ignores the result. Since 5.1 a function declared to return
undefined needs no return statement.
In unions and intersections, never disappears from a union and
unknown disappears from an intersection, while unknown absorbs
a union and never absorbs an intersection. any absorbs both.
Object types
An object type lists members, separated by semicolons, commas or line breaks. It is
written inline or named with type or
interface.
ObjectType: { {Member (; | , | newline)} }
Member: [readonly] Name [?] : Type
| Name [?] ( Parameters ) [: Type]
| ( Parameters ) : Type
| new ( Parameters ) : Type
| [readonly] [ Name : KeyType ] : Type
| get Name ( ) : Type
| set Name ( Name : Type )
| Modifier | Effect |
|---|---|
name?: T | The property may be missing. Its type also includes undefined unless exactOptionalPropertyTypes is set |
readonly name: T | The property cannot be assigned through this type. It is not checked in assignability, so a readonly property can be assigned to a mutable one |
interface Point {
readonly x: number;
y?: number;
}
const p: Point = { x: 1 };
p.x = 2; // error: read-only
interface Mutable { x: number }
const m: Mutable = p; // allowed: readonly is not checked here
m.x = 2; // and this changes p
readonly applies only to the path through that typeIndex signatures
An index signature gives the type of every property whose name has a given type. The
key type may be string, number, symbol, a
template literal pattern, or a union of those (4.4).
interface Scores {
[name: string]: number;
length: number; // must match the string signature
}
interface Bad {
[key: string]: number;
name: string; // error: string is not assignable to number
}
interface Mixed {
[index: number]: string; // numeric keys must fit the string signature
[key: string]: string | number;
}
interface Attributes {
[attribute: `data-${string}`]: string;
}
Reading through an index signature gives the value type without
undefined, unless
noUncheckedIndexedAccess
is set.
object, {} and Object
| Type | Accepts |
|---|---|
object | Any non-primitive value |
{} | Anything but null and undefined, including primitives |
Object | The same values as {}, with Object’s methods |
Excess property checks
An object literal written directly where a type is expected may not have properties that the type does not know, because this is usually a typing mistake. The check applies only to fresh literals, so the same value in a variable is accepted.
interface Options { port: number }
const a: Options = { port: 80, host: "x" }; // error: host is unknown
const value = { port: 80, host: "x" };
const b: Options = value; // fine
A type whose properties are all optional is a weak type. Assigning a value with no properties in common with a weak type is an error, even from a variable (2.4).
Arrays
T[] and Array<T> are the same type.
readonly T[] and ReadonlyArray<T> have no mutating methods.
A mutable array can be assigned to a read-only array, but not the reverse.
const list: number[] = [1, 2];
const view: readonly number[] = list;
view.push(3); // error
const back: number[] = view; // error
const mixed = [1, "a"]; // (string | number)[]
Tuples
A tuple is an array of a known length, with a type for each position. Each element may be optional or a rest element, and each may have a label for readability. Labels do not affect the type.
TupleType: [readonly] [ [Element {, Element}] ]
Element: [Label :] Type [?] | [Label :] ... Type
| Form | Meaning | Since |
|---|---|---|
[string, number] | Exactly two elements | 1.3 |
[string, number?] | One or two; length is 1 | 2 | 3.0 |
[string, ...number[]] | One string, then any number of numbers | 3.0 |
[...string[], number] | A leading or middle rest element | 4.2 |
[a: string, b?: number] | Labelled elements | 4.0 |
[first: string, number] | Labelled and unlabelled together | 5.2 |
readonly [number, number] | No mutation | 3.4 |
Optional elements cannot come before required ones, and a tuple may have only one rest
element. A tuple can be spread into another with
...T.
Literal types
A literal can be used as a type containing just that value: strings, numbers, negative
numbers, BigInts and true or false. A
union of them makes an enumeration
of allowed values.
type Align = "left" | "right" | "centre";
type Bit = 0 | 1;
type Sign = -1 | 1;
type Big = 10n;
let a = "x"; // string: a let can change
const b = "x"; // "x": a const cannot
const o = { k: "x" }; // { k: string }
const c = { k: "x" } as const; // { readonly k: "x" }
This is called widening. A let, a parameter or a property
initialised from a literal gets the general type, and a const keeps the
literal. To keep the literal, use
as const, or an annotation.
A variable with no annotation and no initialiser starts as any, and the
compiler then tracks each assignment. let a; followed by a = 1
makes a a number until the next assignment. An empty array
literal does the same, and its element type grows with each push.
Structural typing
TypeScript compares types by their shape, not by name or by where they were declared. A value fits a type if it has the members that type requires, and may have others (Handbook: Type Compatibility).
class Cat { name = "Tom" }
class Ship { name = "Queen Mary" }
const c: Cat = new Ship(); // fine: the shapes match
class Vault { private code = 1 }
class Safe { private code = 1 }
const v: Vault = new Safe(); // error: private members come from different classes
private and protected members break the shape ruleAssignability rules
Type S can be assigned to type T when one of these
holds:
SandTare identical, orSisnever, orTisunknownorany.SisanyandTis notnever.Sis a union and every member fitsT, orTis a union andSfits a member.Tis an object type andShas every required member with a type that fits. Private and protected members must come from the same declaration.- A numeric enum and
numberfit each other. Enum members of different enums do not. - Both are functions, compared as below.
Function types compare as follows:
- Parameters: a function with fewer parameters fits one with more,
since extra arguments are ignored. Names do not matter. A rest parameter acts as
endless optional parameters. Under
strictNullChecksan optional parameter’s type includesundefined, so a function that requires the parameter does not fit one where it is optional. - Return type: the source’s return type must fit the target’s,
unless the target returns
void. - Parameter types: under
strictFunctionTypes, a function type property is checked contravariantly. Methods are exempt and stay bivariant, so the two forms below differ.
interface Animal { name: string }
interface Dog extends Animal { bark(): void }
type WithProperty = { handle: (a: Animal) => void };
type WithMethod = { handle(a: Animal): void };
const dogHandler = { handle: (d: Dog) => {} };
const p: WithProperty = dogHandler; // error under strictFunctionTypes
const m: WithMethod = dogHandler; // allowed: methods are bivariant
Generic types are compared by their structure after substitution. An unused type parameter has no effect. Parameter types can be stated with variance annotations.