0% found this document useful (0 votes)
217 views833 pages

Typescript en

Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
217 views833 pages

Typescript en

Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 833

TYPESCRIPT Docs - English

TYPESCRIPT Docs - English

1
typescript

Table of contents
En - Nightly Builds 6
Declaration files - By Example 8
Declaration files - Consumption 13
Declaration files - Deep Dive 14
Declaration files - Do's and Don'ts 18
Declaration files - Introduction 22
Declaration files - Library Structures 24
Declaration files - Publishing 30
Declaration files - Templates 34
Templates - Global modifying module.d.ts 35
Templates - Global plugin.d.ts 37
Templates - Global.d.ts 43
Templates - Module class.d.ts 46
Templates - Module function.d.ts 48
Templates - Module plugin.d.ts 50
Templates - Module.d.ts 52
Get started - TS for Functional Programmers 58
Get started - TS for JS Programmers 68
Get started - TS for OOPers 74
Get started - TS for the New Programmer 79
Handbook v1 - Basic Types 83
Handbook v1 - Classes 91
Handbook v1 - Functions 102
Handbook v1 - Generics 112
Handbook v1 - Interfaces 119
Handbook v1 - Literal Types 132
Handbook v1 - Unions and Intersections 134
Handbook v2 - Basics 140
Handbook v2 - Classes 149
Handbook v2 - Everyday Types 174
Handbook v2 - Modules 188
Handbook v2 - More on Functions 196
Handbook v2 - Narrowing 212
Handbook v2 - Object Types 226

2
TYPESCRIPT Docs - English

Handbook v2 - The Handbook 242


Handbook v2 - Type Declarations 244
Type Manipulation - Conditional Types 247
Type Manipulation - Generics 252
Type Manipulation - Indexed Access Types 259
Type Manipulation - Keyof Type Operator 261
Type Manipulation - Mapped Types 262
Type Manipulation - Template Literal Types 265
Type Manipulation - Typeof Type Operator 270
Type Manipulation - Creating Types from Types 271
Handbook v2 - Understanding Errors 272
Javascript - Creating DTS files From JS 274
Javascript - Intro to JS with TS 276
Javascript - JSDoc Reference 278
Javascript - Type Checking JavaScript Files 292
Project config - Compiler Options 298
Project config - Compiler Options in MSBuild 317
Project config - Configuring Watch 326
Project config - Integrating with Build Tools 329
Project config - Project References 334
Project config - Tsconfig.json 339
Reference - Advanced Types 342
Reference - Declaration Merging 362
Reference - Decorators 368
Reference - ESM Support for Node 380
Reference - Enums 385
Reference - Iterators and Generators 393
Reference - JSX 395
Reference - Mixins 404
Reference - Module Resolution 409
Reference - Modules 420
Reference - Namespaces 436
Reference - Namespaces and Modules 441
Reference - Symbols 444
Reference - Triple Slash Directives 447
Reference - Type Compatibility 450

3
typescript

Reference - Type Inference 457


Reference - Utility Types 460
Reference - Variable Declarations 469
Release notes - TypeScript 1.1 481
Release notes - TypeScript 1.3 482
Release notes - TypeScript 1.4 483
Release notes - TypeScript 1.5 488
Release notes - TypeScript 1.6 496
Release notes - TypeScript 1.7 505
Release notes - TypeScript 1.8 509
Release notes - TypeScript 2.0 522
Release notes - TypeScript 2.1 540
Release notes - TypeScript 2.2 550
Release notes - TypeScript 2.3 555
Release notes - TypeScript 2.4 560
Release notes - TypeScript 2.5 563
Release notes - TypeScript 2.6 565
Release notes - TypeScript 2.7 571
Release notes - TypeScript 2.8 579
Release notes - TypeScript 2.9 587
Release notes - TypeScript 3.0 593
Release notes - TypeScript 3.1 600
Release notes - TypeScript 3.2 602
Release notes - TypeScript 3.3 607
Release notes - TypeScript 3.4 610
Release notes - TypeScript 3.5 618
Release notes - TypeScript 3.6 623
Release notes - TypeScript 3.7 630
Release notes - TypeScript 3.8 646
Release notes - TypeScript 3.9 655
Release notes - TypeScript 4.0 667
Release notes - TypeScript 4.1 683
Release notes - TypeScript 4.2 695
Release notes - TypeScript 4.3 709
Release notes - TypeScript 4.4 727
Release notes - TypeScript 4.5 742

4
TYPESCRIPT Docs - English

Release notes - TypeScript 4.6 752


Release notes - TypeScript 4.7 761
Release notes - TypeScript 4.8 779
Release notes - TypeScript 4.9 789
Tutorials - ASP.NET Core 799
Tutorials - Angular 804
Tutorials - Babel with TypeScript 805
Tutorials - DOM Manipulation 807
Tutorials - Gulp 812
Tutorials - Migrating from JavaScript 820
Tutorials - React 828
Tutorials - TypeScript Tooling in 5 minutes 829

5
typescript

A nightly build from the TypeScript's main branch is published by midnight PST to npm. Here is how you
can get it and use it with your tools.

Using npm
npm install -g typescript@next

Updating your IDE to use the nightly builds


You can also update your IDE to use the nightly drop. First you will need to install the package through
npm. You can either install the npm package globally or to a local node_modules folder.

The rest of this section assumes typescript@next is already installed.

Visual Studio Code


Update .vscode/settings.json with the following:

"typescript.tsdk": "<path to your folder>/node_modules/typescript/lib"

More information is available at VSCode documentation.

Sublime Text
Update the Settings - User file with the following:

"typescript_tsdk": "<path to your folder>/node_modules/typescript/lib"

More information is available at the TypeScript Plugin for Sublime Text installation documentation.

Visual Studio 2013 and 2015

Note: Most changes do not require you to install a new version of the VS TypeScript plugin.

The nightly build currently does not include the full plugin setup, but we are working on publishing an in-
staller on a nightly basis as well.

1. Download the VSDevMode.ps1 script.

Also see our wiki page on using a custom language service file.

2. From a PowerShell command window, run:

For VS 2015:

6
Nightly Builds

VSDevMode.ps1 14 -tsScript <path to your folder>/node_modules/typescript/lib

For VS 2013:

VSDevMode.ps1 12 -tsScript <path to your folder>/node_modules/typescript/lib

IntelliJ IDEA (Mac)


Go to Preferences > Languages & Frameworks > TypeScript :

TypeScript Version: If you installed with npm: /usr/local/lib/node_modules/typescript/lib

IntelliJ IDEA (Windows)


Go to File > Settings > Languages & Frameworks > TypeScript :

TypeScript Version: If you installed with npm: C:\Users\USERNAME\AppData\Roaming\npm\node_mod


ules\typescript\lib

Go to TOC

7
typescript

The purpose of this guide is to teach you how to write a high-quality definition file. This guide is structured
by showing documentation for some API, along with sample usage of that API, and explaining how to write
the corresponding declaration.

These examples are ordered in approximately increasing order of complexity.

Objects with Properties


Documentation

The global variable myLib has a function makeGreeting for creating greetings, and a property numb
erOfGreetings indicating the number of greetings made so far.

Code

let result = myLib.makeGreeting("hello, world");


console.log("The computed greeting is:" + result);

let count = myLib.numberOfGreetings;

Declaration

Use declare namespace to describe types or values accessed by dotted notation.

declare namespace myLib {


function makeGreeting(s: string): string;
let numberOfGreetings: number;
}

Overloaded Functions
Documentation

The getWidget function accepts a number and returns a Widget, or accepts a string and returns a Widget
array.

Code

let x: Widget = getWidget(43);

let arr: Widget[] = getWidget("all of them");

Declaration

declare function getWidget(n: number): Widget;


declare function getWidget(s: string): Widget[];

8
Declaration Reference

Reusable Types (Interfaces)


Documentation

When specifying a greeting, you must pass a GreetingSettings object. This object has the following
properties:

1 - greeting: Mandatory string

2 - duration: Optional length of time (in milliseconds)

3 - color: Optional string, e.g. '#ff00ff'

Code

greet({
greeting: "hello world",
duration: 4000
});

Declaration

Use an interface to define a type with properties.

interface GreetingSettings {
greeting: string;
duration?: number;
color?: string;
}

declare function greet(setting: GreetingSettings): void;

Reusable Types (Type Aliases)


Documentation

Anywhere a greeting is expected, you can provide a string , a function returning a string , or a Gr
eeter instance.

Code

function getGreeting() {
return "howdy";
}
class MyGreeter extends Greeter {}

9
typescript

greet("hello");
greet(getGreeting);
greet(new MyGreeter());

Declaration

You can use a type alias to make a shorthand for a type:

type GreetingLike = string | (() => string) | MyGreeter;

declare function greet(g: GreetingLike): void;

Organizing Types
Documentation

The greeter object can log to a file or display an alert. You can provide LogOptions to .log(...)
and alert options to .alert(...)

Code

const g = new Greeter("Hello");


g.log({ verbose: true });
g.alert({ modal: false, title: "Current Greeting" });

Declaration

Use namespaces to organize types.

declare namespace GreetingLib {


interface LogOptions {
verbose?: boolean;
}
interface AlertOptions {
modal: boolean;
title?: string;
color?: string;
}
}

You can also create nested namespaces in one declaration:

declare namespace GreetingLib.Options {


// Refer to via GreetingLib.Options.Log
interface Log {
verbose?: boolean;
}
interface Alert {
modal: boolean;
title?: string;
color?: string;
}
}

10
Declaration Reference

Classes
Documentation

You can create a greeter by instantiating the Greeter object, or create a customized greeter by ex-
tending from it.

Code

const myGreeter = new Greeter("hello, world");


myGreeter.greeting = "howdy";
myGreeter.showGreeting();

class SpecialGreeter extends Greeter {


constructor() {
super("Very special greetings");
}
}

Declaration

Use declare class to describe a class or class-like object. Classes can have properties and methods as
well as a constructor.

declare class Greeter {


constructor(greeting: string);

greeting: string;
showGreeting(): void;
}

Global Variables
Documentation

The global variable foo contains the number of widgets present.

Code

console.log("Half the number of widgets is " + foo / 2);

Declaration

Use declare var to declare variables. If the variable is read-only, you can use declare const . You can
also use declare let if the variable is block-scoped.

11
typescript

/** The number of widgets present */


declare var foo: number;

Global Functions
Documentation

You can call the function greet with a string to show a greeting to the user.

Code

greet("hello, world");

Declaration

Use declare function to declare functions.

declare function greet(greeting: string): void;

Go to TOC

12
Consumption

Downloading
Getting type declarations requires no tools apart from npm.

As an example, getting the declarations for a library like lodash takes nothing more than the following
command

npm install --save-dev @types/lodash

It is worth noting that if the npm package already includes its declaration file as described in Publishing,
downloading the corresponding @types package is not needed.

Consuming
From there you’ll be able to use lodash in your TypeScript code with no fuss. This works for both modules
and global code.

For example, once you’ve npm install -ed your type declarations, you can use imports and write

import * as _ from "lodash";


_.padStart("Hello TypeScript!", 20, " ");

or if you’re not using modules, you can just use the global variable _ .

_.padStart("Hello TypeScript!", 20, " ");

Searching
For the most part, type declaration packages should always have the same name as the package name on
npm , but prefixed with @types/ , but if you need, you can check out this Type Search to find the package
for your favorite library.

Note: if the declaration file you are searching for is not present, you can always contribute one back
and help out the next developer looking for it. Please see the DefinitelyTyped contribution guidelines
page for details.

Go to TOC

13
typescript

Declaration File Theory: A Deep Dive


Structuring modules to give the exact API shape you want can be tricky. For example, we might want a
module that can be invoked with or without new to produce different types, has a variety of named types
exposed in a hierarchy, and has some properties on the module object as well.

By reading this guide, you'll have the tools to write complex declaration files that expose a friendly API sur-
face. This guide focuses on module (or UMD) libraries because the options here are more varied.

Key Concepts
You can fully understand how to make any shape of declaration by understanding some key concepts of how
TypeScript works.

Types
If you're reading this guide, you probably already roughly know what a type in TypeScript is. To be more ex-
plicit, though, a type is introduced with:

A type alias declaration ( type sn = number | string; )


An interface declaration ( interface I { x: number[]; } )
A class declaration ( class C { } )
An enum declaration ( enum E { A, B, C } )
An import declaration which refers to a type

Each of these declaration forms creates a new type name.

Values
As with types, you probably already understand what a value is. Values are runtime names that we can ref-
erence in expressions. For example let x = 5; creates a value called x .

Again, being explicit, the following things create values:

let , const , and var declarations


A namespace or module declaration which contains a value
An enum declaration
A class declaration
An import declaration which refers to a value
A function declaration

Namespaces
Types can exist in namespaces. For example, if we have the declaration let x: A.B.C , we say that the
type C comes from the A.B namespace.

This distinction is subtle and important -- here, A.B is not necessarily a type or a value.

14
Deep Dive

Simple Combinations: One name, multiple meanings


Given a name A , we might find up to three different meanings for A : a type, a value or a namespace. How
the name is interpreted depends on the context in which it is used. For example, in the declaration let m:
A.A = A; , A is used first as a namespace, then as a type name, then as a value. These meanings might
end up referring to entirely different declarations!

This may seem confusing, but it's actually very convenient as long as we don't excessively overload things.
Let's look at some useful aspects of this combining behavior.

Built-in Combinations
Astute readers will notice that, for example, class appeared in both the type and value lists. The declara-
tion class C { } creates two things: a type C which refers to the instance shape of the class, and a val-
ue C which refers to the constructor function of the class. Enum declarations behave similarly.

User Combinations
Let's say we wrote a module file foo.d.ts :

export var SomeVar: { a: SomeType };


export interface SomeType {
count: number;
}

Then consumed it:

import * as foo from "./foo";


let x: foo.SomeType = foo.SomeVar.a;
console.log(x.count);

This works well enough, but we might imagine that SomeType and SomeVar were very closely related such
that you'd like them to have the same name. We can use combining to present these two different objects
(the value and the type) under the same name Bar :

export var Bar: { a: Bar };


export interface Bar {
count: number;
}

This presents a very good opportunity for destructuring in the consuming code:

import { Bar } from "./foo";


let x: Bar = Bar.a;
console.log(x.count);

Again, we've used Bar as both a type and a value here. Note that we didn't have to declare the Bar value
as being of the Bar type -- they're independent.

15
typescript

Advanced Combinations
Some kinds of declarations can be combined across multiple declarations. For example, class C { } and
interface C { } can co-exist and both contribute properties to the C types.

This is legal as long as it does not create a conflict. A general rule of thumb is that values always conflict
with other values of the same name unless they are declared as namespace s, types will conflict if they are
declared with a type alias declaration ( type s = string ), and namespaces never conflict.

Let's see how this can be used.

Adding using an interface


We can add additional members to an interface with another interface declaration:

interface Foo {
x: number;
}
// ... elsewhere ...
interface Foo {
y: number;
}
let a: Foo = ...;
console.log(a.x + a.y); // OK

This also works with classes:

class Foo {
x: number;
}
// ... elsewhere ...
interface Foo {
y: number;
}
let a: Foo = ...;
console.log(a.x + a.y); // OK

Note that we cannot add to type aliases ( type s = string; ) using an interface.

Adding using a namespace


A namespace declaration can be used to add new types, values, and namespaces in any way which does
not create a conflict.

For example, we can add a static member to a class:

class C {}
// ... elsewhere ...
namespace C {
export let x: number;
}
let y = C.x; // OK

16
Deep Dive

Note that in this example, we added a value to the static side of C (its constructor function). This is be-
cause we added a value, and the container for all values is another value (types are contained by name-
spaces, and namespaces are contained by other namespaces).

We could also add a namespaced type to a class:

class C {}
// ... elsewhere ...
namespace C {
export interface D {}
}
let y: C.D; // OK

In this example, there wasn't a namespace C until we wrote the namespace declaration for it. The mean-
ing C as a namespace doesn't conflict with the value or type meanings of C created by the class.

Finally, we could perform many different merges using namespace declarations. This isn't a particularly re-
alistic example, but shows all sorts of interesting behavior:

namespace X {
export interface Y {}
export class Z {}
}

// ... elsewhere ...


namespace X {
export var Y: number;
export namespace Z {
export class C {}
}
}
type X = string;

In this example, the first block creates the following name meanings:

A value X (because the namespace declaration contains a value, Z )


A namespace X (because the namespace declaration contains a type, Y )
A type Y in the X namespace
A type Z in the X namespace (the instance shape of the class)
A value Z that is a property of the X value (the constructor function of the class)

The second block creates the following name meanings:

A value Y (of type number ) that is a property of the X value


A namespace Z
A value Z that is a property of the X value
A type C in the X.Z namespace
A value C that is a property of the X.Z value
A type X

Go to TOC

17
typescript

General Types
Number , String , Boolean , Symbol and Object
❌ Don't ever use the types Number , String , Boolean , Symbol , or Object These types refer to non-
primitive boxed objects that are almost never used appropriately in JavaScript code.

/* WRONG */
function reverse(s: String): String;

✅ Do use the types number , string , boolean , and symbol .

/* OK */
function reverse(s: string): string;

Instead of Object , use the non-primitive object type (added in TypeScript 2.2).

Generics
❌ Don't ever have a generic type which doesn't use its type parameter. See more details in TypeScript FAQ
page.

any
❌ Don't use any as a type unless you are in the process of migrating a JavaScript project to TypeScript.
The compiler effectively treats any as "please turn off type checking for this thing". It is similar to putting
an @ts-ignore comment around every usage of the variable. This can be very helpful when you are first
migrating a JavaScript project to TypeScript as you can set the type for stuff you haven't migrated yet as
any , but in a full TypeScript project you are disabling type checking for any parts of your program that use
it.

In cases where you don't know what type you want to accept, or when you want to accept anything because
you will be blindly passing it through without interacting with it, you can use unknown .

Callback Types
Return Types of Callbacks
❌ Don't use the return type any for callbacks whose value will be ignored:

/* WRONG */
function fn(x: () => any) {
x();
}

✅ Do use the return type void for callbacks whose value will be ignored:

18
Do's and Don'ts

/* OK */
function fn(x: () => void) {
x();
}

❔ Why: Using void is safer because it prevents you from accidentally using the return value of x in an
unchecked way:

function fn(x: () => void) {


var k = x(); // oops! meant to do something else
k.doSomething(); // error, but would be OK if the return type had been 'any'
}

Optional Parameters in Callbacks


❌ Don't use optional parameters in callbacks unless you really mean it:

/* WRONG */
interface Fetcher {
getObject(done: (data: unknown, elapsedTime?: number) => void): void;
}

This has a very specific meaning: the done callback might be invoked with 1 argument or might be invoked
with 2 arguments. The author probably intended to say that the callback might not care about the
elapsedTime parameter, but there's no need to make the parameter optional to accomplish this -- it's al-
ways legal to provide a callback that accepts fewer arguments.

✅ Do write callback parameters as non-optional:

/* OK */
interface Fetcher {
getObject(done: (data: unknown, elapsedTime: number) => void): void;
}

Overloads and Callbacks


❌ Don't write separate overloads that differ only on callback arity:

/* WRONG */
declare function beforeAll(action: () => void, timeout?: number): void;
declare function beforeAll(
action: (done: DoneFn) => void,
timeout?: number
): void;

✅ Do write a single overload using the maximum arity:

/* OK */
declare function beforeAll(
action: (done: DoneFn) => void,
timeout?: number
): void;

19
typescript

❔ Why: It's always legal for a callback to disregard a parameter, so there's no need for the shorter over-
load. Providing a shorter callback first allows incorrectly-typed functions to be passed in because they match
the first overload.

Function Overloads
Ordering
❌ Don't put more general overloads before more specific overloads:

/* WRONG */
declare function fn(x: unknown): unknown;
declare function fn(x: HTMLElement): number;
declare function fn(x: HTMLDivElement): string;

var myElem: HTMLDivElement;


var x = fn(myElem); // x: unknown, wat?

✅ Do sort overloads by putting the more general signatures after more specific signatures:

/* OK */
declare function fn(x: HTMLDivElement): string;
declare function fn(x: HTMLElement): number;
declare function fn(x: unknown): unknown;

var myElem: HTMLDivElement;


var x = fn(myElem); // x: string, :)

❔ Why: TypeScript chooses the first matching overload when resolving function calls. When an earlier
overload is "more general" than a later one, the later one is effectively hidden and cannot be called.

Use Optional Parameters


❌ Don't write several overloads that differ only in trailing parameters:

/* WRONG */
interface Example {
diff(one: string): number;
diff(one: string, two: string): number;
diff(one: string, two: string, three: boolean): number;
}

✅ Do use optional parameters whenever possible:

/* OK */
interface Example {
diff(one: string, two?: string, three?: boolean): number;
}

Note that this collapsing should only occur when all overloads have the same return type.

❔ Why: This is important for two reasons.

20
Do's and Don'ts

TypeScript resolves signature compatibility by seeing if any signature of the target can be invoked with the
arguments of the source, and extraneous arguments are allowed. This code, for example, exposes a bug
only when the signature is correctly written using optional parameters:

function fn(x: (a: string, b: number, c: number) => void) {}


var x: Example;
// When written with overloads, OK -- used first overload
// When written with optionals, correctly an error
fn(x.diff);

The second reason is when a consumer uses the "strict null checking" feature of TypeScript. Because un-
specified parameters appear as undefined in JavaScript, it's usually fine to pass an explicit undefined to
a function with optional arguments. This code, for example, should be OK under strict nulls:

var x: Example;
// When written with overloads, incorrectly an error because of passing
'undefined' to 'string'
// When written with optionals, correctly OK
x.diff("something", true ? undefined : "hour");

Use Union Types


❌ Don't write overloads that differ by type in only one argument position:

/* WRONG */
interface Moment {
utcOffset(): number;
utcOffset(b: number): Moment;
utcOffset(b: string): Moment;
}

✅ Do use union types whenever possible:

/* OK */
interface Moment {
utcOffset(): number;
utcOffset(b: number | string): Moment;
}

Note that we didn't make b optional here because the return types of the signatures differ.

❔ Why: This is important for people who are "passing through" a value to your function:

function fn(x: string): void;


function fn(x: number): void;
function fn(x: number | string) {
// When written with separate overloads, incorrectly an error
// When written with union types, correctly OK
return moment().utcOffset(x);
}

Go to TOC

21
typescript

The Declaration Files section is designed to teach you how to write a high-quality TypeScript Declaration
File. We need to assume basic familiarity with the TypeScript language in order to get started.

If you haven't already, you should read the TypeScript Handbook to familiarize yourself with basic concepts,
especially types and modules.

The most common case for learning how .d.ts files work is that you're typing an npm package with no
types. In that case, you can jump straight to Modules .d.ts.

The Declaration Files section is broken down into the following sections.

Declaration Reference
We are often faced with writing a declaration file when we only have examples of the underlying library to
guide us. The Declaration Reference section shows many common API patterns and how to write declara-
tions for each of them. This guide is aimed at the TypeScript novice who may not yet be familiar with every
language construct in TypeScript.

Library Structures
The Library Structures guide helps you understand common library formats and how to write a proper dec-
laration file for each format. If you're editing an existing file, you probably don't need to read this section.
Authors of new declaration files are strongly encouraged to read this section to properly understand how the
format of the library influences the writing of the declaration file.

In the Template section you'll find a number of declaration files that serve as a useful starting point when
writing a new file. If you already know what your structure is, see the d.ts Template section in the sidebar.

Do's and Don'ts


Many common mistakes in declaration files can be easily avoided. The Do's and Don'ts section identifies
common errors, describes how to detect them, and how to fix them. Everyone should read this section to
help themselves avoid common mistakes.

Deep Dive
For seasoned authors interested in the underlying mechanics of how declaration files work, the Deep Dive
section explains many advanced concepts in declaration writing, and shows how to leverage these concepts
to create cleaner and more intuitive declaration files.

Publish to npm
The Publishing section explains how to publish your declaration files to an npm package, and shows how to
manage your dependent packages.

22
Introduction

Find and Install Declaration Files


For JavaScript library users, the Consumption section offers a few simple steps to locate and install corre-
sponding declaration files.

Go to TOC

23
typescript

Broadly speaking, the way you structure your declaration file depends on how the library is consumed.
There are many ways of offering a library for consumption in JavaScript, and you'll need to write your decla-
ration file to match it. This guide covers how to identify common library patterns, and how to write declara-
tion files which correspond to that pattern.

Each type of major library structuring pattern has a corresponding file in the Templates section. You can
start with these templates to help you get going faster.

Identifying Kinds of Libraries


First, we'll review the kinds of libraries TypeScript declaration files can represent. We'll briefly show how
each kind of library is used, how it is written, and list some example libraries from the real world.

Identifying the structure of a library is the first step in writing its declaration file. We'll give hints on how to
identify structure both based on its usage and its code. Depending on the library's documentation and orga-
nization, one might be easier than the other. We recommend using whichever is more comfortable to you.

What should you look for?


Question to ask yourself while looking at a library you are trying to type.

1. How do you obtain the library?

For example, can you only get it through npm or only from a CDN?

2. How would you import it?

Does it add a global object? Does it use require or import / export statements?

Smaller samples for different types of libraries


Modular Libraries
Almost every modern Node.js library falls into the module family. These type of libraries only work in a JS
environment with a module loader. For example, express only works in Node.js and must be loaded using
the CommonJS require function.

ECMAScript 2015 (also known as ES2015, ECMAScript 6, and ES6), CommonJS, and RequireJS have similar
notions of importing a module. In JavaScript CommonJS (Node.js), for example, you would write

var fs = require("fs");

In TypeScript or ES6, the import keyword serves the same purpose:

import * as fs from "fs";

You'll typically see modular libraries include one of these lines in their documentation:

24
Library Structures

var someLib = require("someLib");

or

define(..., ['someLib'], function(someLib) {

});

As with global modules, you might see these examples in the documentation of a UMD module, so be sure
to check the code or documentation.

Identifying a Module Library from Code


Modular libraries will typically have at least some of the following:

Unconditional calls to require or define


Declarations like import * as a from 'b'; or export c;
Assignments to exports or module.exports

They will rarely have:

Assignments to properties of window or global

Templates For Modules


There are four templates available for modules, module.d.ts , module-class.d.ts , module-func‐
tion.d.ts and module-plugin.d.ts .

You should first read module.d.ts for an overview on the way they all work.

Then use the template module-function.d.ts if your module can be called like a function:

const x = require("foo");
// Note: calling 'x' as a function
const y = x(42);

Use the template module-class.d.ts if your module can be constructed using new :

const x = require("bar");
// Note: using 'new' operator on the imported variable
const y = new x("hello");

If you have a module which when imported, makes changes to other modules use template module-
plugin.d.ts :

const jest = require("jest");


require("jest-matchers-files");

25
typescript

Global Libraries
A global library is one that can be accessed from the global scope (i.e. without using any form of import ).
Many libraries simply expose one or more global variables for use. For example, if you were using jQuery,
the $ variable can be used by simply referring to it:

$(() => {
console.log("hello!");
});

You'll usually see guidance in the documentation of a global library of how to use the library in an HTML
script tag:

<script src="http://a.great.cdn.for/someLib.js"></script>

Today, most popular globally-accessible libraries are actually written as UMD libraries (see below). UMD li-
brary documentation is hard to distinguish from global library documentation. Before writing a global decla-
ration file, make sure the library isn't actually UMD.

Identifying a Global Library from Code


Global library code is usually extremely simple. A global "Hello, world" library might look like this:

function createGreeting(s) {
return "Hello, " + s;
}

or like this:

// Web
window.createGreeting = function (s) {
return "Hello, " + s;
};

// Node
global.createGreeting = function (s) {
return "Hello, " + s;
};

// Potentially any runtime


globalThis.createGreeting = function (s) {
return "Hello, " + s;
};

When looking at the code of a global library, you'll usually see:

Top-level var statements or function declarations


One or more assignments to window.someName
Assumptions that DOM primitives like document or window exist

You won't see:

Checks for, or usage of, module loaders like require or define

26
Library Structures

CommonJS/Node.js-style imports of the form var fs = require("fs");


Calls to define(...)
Documentation describing how to require or import the library

Examples of Global Libraries


Because it's usually easy to turn a global library into a UMD library, very few popular libraries are still writ-
ten in the global style. However, libraries that are small and require the DOM (or have no dependencies)
may still be global.

Global Library Template


The template file global.d.ts defines an example library myLib . Be sure to read the "Preventing Name
Conflicts" footnote.

UMD
A UMD module is one that can either be used as module (through an import), or as a global (when run in an
environment without a module loader). Many popular libraries, such as Moment.js, are written this way. For
example, in Node.js or using RequireJS, you would write:

import moment = require("moment");


console.log(moment.format());

whereas in a vanilla browser environment you would write:

console.log(moment.format());

Identifying a UMD library


UMD modules check for the existence of a module loader environment. This is an easy-to-spot pattern that
looks something like this:

(function (root, factory) {


if (typeof define === "function" && define.amd) {
define(["libName"], factory);
} else if (typeof module === "object" && module.exports) {
module.exports = factory(require("libName"));
} else {
root.returnExports = factory(root.libName);
}
}(this, function (b) {

If you see tests for typeof define , typeof window , or typeof module in the code of a library, especial-
ly at the top of the file, it's almost always a UMD library.

Documentation for UMD libraries will also often demonstrate a "Using in Node.js" example showing re‐
quire , and a "Using in the browser" example showing using a <script> tag to load the script.

27
typescript

Examples of UMD libraries


Most popular libraries are now available as UMD packages. Examples include jQuery, Moment.js, lodash,
and many more.

Template
Use the module-plugin.d.ts template.

Consuming Dependencies
There are several kinds of dependencies your library might have. This section shows how to import them
into the declaration file.

Dependencies on Global Libraries


If your library depends on a global library, use a /// <reference types="..." /> directive:

/// <reference types="someLib" />

function getThing(): someLib.thing;

Dependencies on Modules
If your library depends on a module, use an import statement:

import * as moment from "moment";

function getThing(): moment;

Dependencies on UMD libraries


From a Global Library
If your global library depends on a UMD module, use a /// <reference types directive:

/// <reference types="moment" />

function getThing(): moment;

From a Module or UMD Library


If your module or UMD library depends on a UMD library, use an import statement:

import * as someLib from "someLib";

Do not use a /// <reference directive to declare a dependency to a UMD library!

28
Library Structures

Footnotes
Preventing Name Conflicts
Note that it's possible to define many types in the global scope when writing a global declaration file. We
strongly discourage this as it leads to possible unresolvable name conflicts when many declaration files are
in a project.

A simple rule to follow is to only declare types namespaced by whatever global variable the library defines.
For example, if the library defines the global value 'cats', you should write

declare namespace cats {


interface KittySettings {}
}

But not

// at top-level
interface CatsKittySettings {}

This guidance also ensures that the library can be transitioned to UMD without breaking declaration file
users.

The Impact of ES6 on Module Call Signatures


Many popular libraries, such as Express, expose themselves as a callable function when imported. For ex-
ample, the typical Express usage looks like this:

import exp = require("express");


var app = exp();

In ES6-compliant module loaders, the top-level object (here imported as exp ) can only have properties;
the top-level module object can never be callable.

The most common solution here is to define a default export for a callable/constructable object; module
loaders commonly detect this situation automatically and replace the top-level object with the default ex-
port. TypeScript can handle this for you, if you have "esModuleInterop": true in your tsconfig.json.

Go to TOC

29
typescript

Now that you have authored a declaration file following the steps of this guide, it is time to publish it to
npm. There are two main ways you can publish your declaration files to npm:

1. bundling with your npm package


2. publishing to the @types organization on npm.

If your types are generated by your source code, publish the types with your source code. Both TypeScript
and JavaScript projects can generate types via declaration .

Otherwise, we recommend submitting the types to DefinitelyTyped, which will publish them to the @types
organization on npm.

Including declarations in your npm package


If your package has a main .js file, you will need to indicate the main declaration file in your package.j‐
son file as well. Set the types property to point to your bundled declaration file. For example:

{
"name": "awesome",
"author": "Vandelay Industries",
"version": "1.0.0",
"main": "./lib/main.js",
"types": "./lib/main.d.ts"
}

Note that the "typings" field is synonymous with types , and could be used as well.

Also note that if your main declaration file is named index.d.ts and lives at the root of the package (next
to index.js ) you do not need to mark the types property, though it is advisable to do so.

Dependencies
All dependencies are managed by npm. Make sure all the declaration packages you depend on are marked
appropriately in the "dependencies" section in your package.json . For example, imagine we authored a
package that used Browserify and TypeScript.

{
"name": "browserify-typescript-extension",
"author": "Vandelay Industries",
"version": "1.0.0",
"main": "./lib/main.js",
"types": "./lib/main.d.ts",
"dependencies": {
"browserify": "latest",
"@types/browserify": "latest",
"typescript": "next"
}
}

30
Publishing

Here, our package depends on the browserify and typescript packages. browserify does not bundle
its declaration files with its npm packages, so we needed to depend on @types/browserify for its declara-
tions. typescript , on the other hand, packages its declaration files, so there was no need for any addi-
tional dependencies.

Our package exposes declarations from each of those, so any user of our browserify-typescript-exten‐
sion package needs to have these dependencies as well. For that reason, we used "dependencies" and
not "devDependencies" , because otherwise our consumers would have needed to manually install those
packages. If we had just written a command line application and not expected our package to be used as a
library, we might have used devDependencies .

Red flags
/// <reference path="..." />
Don't use /// <reference path="..." /> in your declaration files.

/// <reference path="../typescript/lib/typescriptServices.d.ts" />


....

Do use /// <reference types="..." /> instead.

/// <reference types="typescript" />


....

Make sure to revisit the Consuming dependencies section for more information.

Packaging dependent declarations


If your type definitions depend on another package:

Don't combine it with yours, keep each in their own file.


Don't copy the declarations in your package either.
Do depend on the npm type declaration package if it doesn't package its declaration files.

Version selection with typesVersions


When TypeScript opens a package.json file to figure out which files it needs to read, it first looks at a field
called typesVersions .

Folder redirects (using * )

A package.json with a typesVersions field might look like this:

{
"name": "package-name",
"version": "1.0.0",
"types": "./index.d.ts",
"typesVersions": {

31
typescript

">=3.1": { "*": ["ts3.1/*"] }


}
}

This package.json tells TypeScript to first check the current version of TypeScript. If it's 3.1 or later,
TypeScript figures out the path you've imported relative to the package, and reads from the package's
ts3.1 folder.

That's what that { "*": ["ts3.1/*"] } means - if you're familiar with path mapping, it works exactly like
that.

In the above example, if we're importing from "package-name" , TypeScript will try to resolve from
[...]/node_modules/package-name/ts3.1/index.d.ts (and other relevant paths) when running in
TypeScript 3.1. If we import from package-name/foo , we'll try to look for [...]/node_modules/package-
name/ts3.1/foo.d.ts and [...]/node_modules/package-name/ts3.1/foo/index.d.ts .

What if we're not running in TypeScript 3.1 in this example? Well, if none of the fields in typesVersions
get matched, TypeScript falls back to the types field, so here TypeScript 3.0 and earlier will be redirected
to [...]/node_modules/package-name/index.d.ts .

File redirects

When you want to only change the resolution for a single file at a time, you can tell TypeScript the file to re-
solve differently by passing in the exact filenames:

{
"name": "package-name",
"version": "1.0.0",
"types": "./index.d.ts",
"typesVersions": {
"<4.0": { "index.d.ts": ["index.v3.d.ts"] }
}
}

On TypeScript 4.0 and above, an import for "package-name" would resolve to ./index.d.ts and for 3.9
and below "./index.v3.d.ts .

Matching behavior
The way that TypeScript decides on whether a version of the compiler & language matches is by using
Node's semver ranges.

Multiple fields
typesVersions can support multiple fields where each field name is specified by the range to match on.

{
"name": "package-name",
"version": "1.0",
"types": "./index.d.ts",
"typesVersions": {

32
Publishing

">=3.2": { "*": ["ts3.2/*"] },


">=3.1": { "*": ["ts3.1/*"] }
}
}

Since ranges have the potential to overlap, determining which redirect applies is order-specific. That means
in the above example, even though both the >=3.2 and the >=3.1 matchers support TypeScript 3.2 and
above, reversing the order could have different behavior, so the above sample would not be equivalent to
the following.

{
"name": "package-name",
"version": "1.0",
"types": "./index.d.ts",
"typesVersions": {
// NOTE: this doesn't work!
">=3.1": { "*": ["ts3.1/*"] },
">=3.2": { "*": ["ts3.2/*"] }
}
}

Publish to @types
Packages under the @types organization are published automatically from DefinitelyTyped using the types-
publisher tool. To get your declarations published as an @types package, please submit a pull request to
DefinitelyTyped. You can find more details in the contribution guidelines page.

Go to TOC

33
typescript

global-modifying-module.d.ts

global-plugin.d.ts
global.d.ts
module-class.d.ts
module-function.d.ts
module-plugin.d.ts
module.d.ts

Go to TOC

34
Global: Modifying Module

Global-modifying Modules
A global-modifying module alters existing values in the global scope when they are imported. For example,
there might exist a library which adds new members to String.prototype when imported. This pattern is
somewhat dangerous due to the possibility of runtime conflicts, but we can still write a declaration file for it.

Identifying global-modifying modules


Global-modifying modules are generally easy to identify from their documentation. In general, they're simi-
lar to global plugins, but need a require call to activate their effects.

You might see documentation like this:

// 'require' call that doesn't use its return value


var unused = require("magic-string-time");
/* or */
require("magic-string-time");

var x = "hello, world";


// Creates new methods on built-in types
console.log(x.startsWithHello());

var y = [1, 2, 3];


// Creates new methods on built-in types
console.log(y.reverseAndSort());

Here is an example

// Type definitions for [~THE LIBRARY NAME~] [~OPTIONAL VERSION NUMBER~]


// Project: [~THE PROJECT NAME~]
// Definitions by: [~YOUR NAME~] <[~A URL FOR YOU~]>

/*~ This is the global-modifying module template file. You should rename it to
index.d.ts
*~ and place it in a folder with the same name as the module.
*~ For example, if you were writing a file for "super-greeter", this
*~ file should be 'super-greeter/index.d.ts'
*/

/*~ Note: If your global-modifying module is callable or constructable, you'll


*~ need to combine the patterns here with those in the module-class or module-
function
*~ template files
*/
declare global {
/*~ Here, declare things that go in the global namespace, or augment
*~ existing declarations in the global namespace
*/
interface String {
fancyFormat(opts: StringFormatOptions): string;
}
}

/*~ If your module exports types or values, write them as usual */


export interface StringFormatOptions {
fancinessLevel: number;

35
typescript

/*~ For example, declaring a method on the module (in addition to its global side
effects) */
export function doSomething(): void;

/*~ If your module exports nothing, you'll need this line. Otherwise, delete it */
export {};

Go to TOC

36
Global: Plugin

UMD
A UMD module is one that can either be used as module (through an import), or as a global (when run in an
environment without a module loader). Many popular libraries, such as Moment.js, are written this way. For
example, in Node.js or using RequireJS, you would write:

import moment = require("moment");


console.log(moment.format());

whereas in a vanilla browser environment you would write:

console.log(moment.format());

Identifying a UMD library


UMD modules check for the existence of a module loader environment. This is an easy-to-spot pattern that
looks something like this:

(function (root, factory) {


if (typeof define === "function" && define.amd) {
define(["libName"], factory);
} else if (typeof module === "object" && module.exports) {
module.exports = factory(require("libName"));
} else {
root.returnExports = factory(root.libName);
}
}(this, function (b) {

If you see tests for typeof define , typeof window , or typeof module in the code of a library, especial-
ly at the top of the file, it's almost always a UMD library.

Documentation for UMD libraries will also often demonstrate a "Using in Node.js" example showing re‐
quire , and a "Using in the browser" example showing using a <script> tag to load the script.

Examples of UMD libraries


Most popular libraries are now available as UMD packages. Examples include jQuery, Moment.js, lodash,
and many more.

Template
There are three templates available for modules, module.d.ts , module-class.d.ts and module-
function.d.ts .

Use module-function.d.ts if your module can be called like a function:

var x = require("foo");
// Note: calling 'x' as a function
var y = x(42);

Be sure to read the footnote "The Impact of ES6 on Module Call Signatures"

37
typescript

Use module-class.d.ts if your module can be constructed using new :

var x = require("bar");
// Note: using 'new' operator on the imported variable
var y = new x("hello");

The same footnote applies to these modules.

If your module is not callable or constructable, use the module.d.ts file.

Module Plugin or UMD Plugin


A module plugin changes the shape of another module (either UMD or module). For example, in Moment.js,
moment-range adds a new range method to the moment object.

For the purposes of writing a declaration file, you'll write the same code whether the module being changed
is a plain module or UMD module.

Template
Use the module-plugin.d.ts template.

Global Plugin
A global plugin is global code that changes the shape of some global. As with global-modifying modules,
these raise the possibility of runtime conflict.

For example, some libraries add new functions to Array.prototype or String.prototype .

Identifying global plugins


Global plugins are generally easy to identify from their documentation.

You'll see examples that look like this:

var x = "hello, world";


// Creates new methods on built-in types
console.log(x.startsWithHello());

var y = [1, 2, 3];


// Creates new methods on built-in types
console.log(y.reverseAndSort());

Template
Use the global-plugin.d.ts template.

38
Global: Plugin

Global-modifying Modules
A global-modifying module alters existing values in the global scope when they are imported. For example,
there might exist a library which adds new members to String.prototype when imported. This pattern is
somewhat dangerous due to the possibility of runtime conflicts, but we can still write a declaration file for it.

Identifying global-modifying modules


Global-modifying modules are generally easy to identify from their documentation. In general, they're simi-
lar to global plugins, but need a require call to activate their effects.

You might see documentation like this:

// 'require' call that doesn't use its return value


var unused = require("magic-string-time");
/* or */
require("magic-string-time");

var x = "hello, world";


// Creates new methods on built-in types
console.log(x.startsWithHello());

var y = [1, 2, 3];


// Creates new methods on built-in types
console.log(y.reverseAndSort());

Template
Use the global-modifying-module.d.ts template.

Consuming Dependencies
There are several kinds of dependencies your library might have. This section shows how to import them
into the declaration file.

Dependencies on Global Libraries


If your library depends on a global library, use a /// <reference types="..." /> directive:

/// <reference types="someLib" />

function getThing(): someLib.thing;

Dependencies on Modules
If your library depends on a module, use an import statement:

import * as moment from "moment";

function getThing(): moment;

39
typescript

Dependencies on UMD libraries


From a Global Library
If your global library depends on a UMD module, use a /// <reference types directive:

/// <reference types="moment" />

function getThing(): moment;

From a Module or UMD Library


If your module or UMD library depends on a UMD library, use an import statement:

import * as someLib from "someLib";

Do not use a /// <reference directive to declare a dependency to a UMD library!

Footnotes
Preventing Name Conflicts
Note that it's possible to define many types in the global scope when writing a global declaration file. We
strongly discourage this as it leads to possible unresolvable name conflicts when many declaration files are
in a project.

A simple rule to follow is to only declare types namespaced by whatever global variable the library defines.
For example, if the library defines the global value 'cats', you should write

declare namespace cats {


interface KittySettings {}
}

But not

// at top-level
interface CatsKittySettings {}

This guidance also ensures that the library can be transitioned to UMD without breaking declaration file
users.

The Impact of ES6 on Module Plugins


Some plugins add or modify top-level exports on existing modules. While this is legal in CommonJS and
other loaders, ES6 modules are considered immutable and this pattern will not be possible. Because
TypeScript is loader-agnostic, there is no compile-time enforcement of this policy, but developers intending
to transition to an ES6 module loader should be aware of this.

40
Global: Plugin

The Impact of ES6 on Module Call Signatures


Many popular libraries, such as Express, expose themselves as a callable function when imported. For ex-
ample, the typical Express usage looks like this:

import exp = require("express");


var app = exp();

In ES6 module loaders, the top-level object (here imported as exp ) can only have properties; the top-level
module object is never callable. The most common solution here is to define a default export for a
callable/constructable object; some module loader shims will automatically detect this situation and replace
the top-level object with the default export.

Library file layout


The layout of your declaration files should mirror the layout of the library.

A library can consist of multiple modules, such as

myLib
+---- index.js
+---- foo.js
+---- bar
+---- index.js
+---- baz.js

These could be imported as

var a = require("myLib");
var b = require("myLib/foo");
var c = require("myLib/bar");
var d = require("myLib/bar/baz");

Your declaration files should thus be

@types/myLib
+---- index.d.ts
+---- foo.d.ts
+---- bar
+---- index.d.ts
+---- baz.d.ts

// Type definitions for [~THE LIBRARY NAME~] [~OPTIONAL VERSION NUMBER~]


// Project: [~THE PROJECT NAME~]
// Definitions by: [~YOUR NAME~] <[~A URL FOR YOU~]>

/*~ This template shows how to write a global plugin. */

/*~ Write a declaration for the original type and add new members.
*~ For example, this adds a 'toBinaryString' method with overloads to
*~ the built-in number type.
*/
interface Number {
toBinaryString(opts?: MyLibrary.BinaryFormatOptions): string;

41
typescript

toBinaryString(
callback: MyLibrary.BinaryFormatCallback,
opts?: MyLibrary.BinaryFormatOptions
): string;
}

/*~ If you need to declare several types, place them inside a namespace
*~ to avoid adding too many things to the global namespace.
*/
declare namespace MyLibrary {
type BinaryFormatCallback = (n: number) => string;
interface BinaryFormatOptions {
prefix?: string;
padding: number;
}
}

Go to TOC

42
Global .d.ts

Global Libraries
A global library is one that can be accessed from the global scope (i.e. without using any form of import ).
Many libraries simply expose one or more global variables for use. For example, if you were using jQuery,
the $ variable can be used by simply referring to it:

$(() => {
console.log("hello!");
});

You'll usually see guidance in the documentation of a global library of how to use the library in an HTML
script tag:

<script src="http://a.great.cdn.for/someLib.js"></script>

Today, most popular globally-accessible libraries are actually written as UMD libraries (see below). UMD li-
brary documentation is hard to distinguish from global library documentation. Before writing a global decla-
ration file, make sure the library isn't actually UMD.

Identifying a Global Library from Code


Global library code is usually extremely simple. A global "Hello, world" library might look like this:

function createGreeting(s) {
return "Hello, " + s;
}

or like this:

window.createGreeting = function (s) {


return "Hello, " + s;
};

When looking at the code of a global library, you'll usually see:

Top-level var statements or function declarations


One or more assignments to window.someName
Assumptions that DOM primitives like document or window exist

You won't see:

Checks for, or usage of, module loaders like require or define


CommonJS/Node.js-style imports of the form var fs = require("fs");
Calls to define(...)
Documentation describing how to require or import the library

43
typescript

Examples of Global Libraries


Because it's usually easy to turn a global library into a UMD library, very few popular libraries are still writ-
ten in the global style. However, libraries that are small and require the DOM (or have no dependencies)
may still be global.

Global Library Template


You can see an example DTS below:

// Type definitions for [~THE LIBRARY NAME~] [~OPTIONAL VERSION NUMBER~]


// Project: [~THE PROJECT NAME~]
// Definitions by: [~YOUR NAME~] <[~A URL FOR YOU~]>

/*~ If this library is callable (e.g. can be invoked as myLib(3)),


*~ include those call signatures here.
*~ Otherwise, delete this section.
*/
declare function myLib(a: string): string;
declare function myLib(a: number): number;

/*~ If you want the name of this library to be a valid type name,
*~ you can do so here.
*~
*~ For example, this allows us to write 'var x: myLib';
*~ Be sure this actually makes sense! If it doesn't, just
*~ delete this declaration and add types inside the namespace below.
*/
interface myLib {
name: string;
length: number;
extras?: string[];
}

/*~ If your library has properties exposed on a global variable,


*~ place them here.
*~ You should also place types (interfaces and type alias) here.
*/
declare namespace myLib {
//~ We can write 'myLib.timeout = 50;'
let timeout: number;

//~ We can access 'myLib.version', but not change it


const version: string;

//~ There's some class we can create via 'let c = new myLib.Cat(42)'
//~ Or reference e.g. 'function f(c: myLib.Cat) { ... }
class Cat {
constructor(n: number);

//~ We can read 'c.age' from a 'Cat' instance


readonly age: number;

//~ We can invoke 'c.purr()' from a 'Cat' instance


purr(): void;
}

//~ We can declare a variable as

44
Global .d.ts

//~ 'var s: myLib.CatSettings = { weight: 5, name: "Maru" };'


interface CatSettings {
weight: number;
name: string;
tailLength?: number;
}

//~ We can write 'const v: myLib.VetID = 42;'


//~ or 'const v: myLib.VetID = "bob";'
type VetID = string | number;

//~ We can invoke 'myLib.checkCat(c)' or 'myLib.checkCat(c, v);'


function checkCat(c: Cat, s?: VetID);
}

Go to TOC

45
typescript

For example, when you want to work with JavaScript code which looks like:

const Greeter = require("super-greeter");

const greeter = new Greeter();


greeter.greet();

To handle both importing via UMD and modules:

// Type definitions for [~THE LIBRARY NAME~] [~OPTIONAL VERSION NUMBER~]


// Project: [~THE PROJECT NAME~]
// Definitions by: [~YOUR NAME~] <[~A URL FOR YOU~]>

/*~ This is the module template file for class modules.


*~ You should rename it to index.d.ts and place it in a folder with the same name
as the module.
*~ For example, if you were writing a file for "super-greeter", this
*~ file should be 'super-greeter/index.d.ts'
*/

// Note that ES6 modules cannot directly export class objects.


// This file should be imported using the CommonJS-style:
// import x = require('[~THE MODULE~]');
//
// Alternatively, if --allowSyntheticDefaultImports or
// --esModuleInterop is turned on, this file can also be
// imported as a default import:
// import x from '[~THE MODULE~]';
//
// Refer to the TypeScript documentation at
// https://www.typescriptlang.org/docs/handbook/modules.html#export--and-import--
require
// to understand common workarounds for this limitation of ES6 modules.

/*~ If this module is a UMD module that exposes a global variable 'myClassLib'
when
*~ loaded outside a module loader environment, declare that global here.
*~ Otherwise, delete this declaration.
*/
export as namespace "super-greeter";

/*~ This declaration specifies that the class constructor function


*~ is the exported object from the file
*/
export = Greeter;

/*~ Write your module's methods and properties in this class */


declare class Greeter {
constructor(customGreeting?: string);

greet: void;

myMethod(opts: MyClass.MyClassMethodOptions): number;


}

/*~ If you want to expose types from your module as well, you can
*~ place them in this block.
*~
*~ Note that if you decide to include this namespace, the module can be
*~ incorrectly imported as a namespace object, unless

46
Module: Class

*~ --esModuleInterop is turned on:


*~ import * as x from '[~THE MODULE~]'; // WRONG! DO NOT DO THIS!
*/
declare namespace MyClass {
export interface MyClassMethodOptions {
width?: number;
height?: number;
}
}

Go to TOC

47
typescript

For example, when you want to work with JavaScript code which looks like:

import greeter from "super-greeter";

greeter(2);
greeter("Hello world");

To handle both importing via UMD and modules:

// Type definitions for [~THE LIBRARY NAME~] [~OPTIONAL VERSION NUMBER~]


// Project: [~THE PROJECT NAME~]
// Definitions by: [~YOUR NAME~] <[~A URL FOR YOU~]>

/*~ This is the module template file for function modules.


*~ You should rename it to index.d.ts and place it in a folder with the same name
as the module.
*~ For example, if you were writing a file for "super-greeter", this
*~ file should be 'super-greeter/index.d.ts'
*/

// Note that ES6 modules cannot directly export class objects.


// This file should be imported using the CommonJS-style:
// import x = require('[~THE MODULE~]');
//
// Alternatively, if --allowSyntheticDefaultImports or
// --esModuleInterop is turned on, this file can also be
// imported as a default import:
// import x from '[~THE MODULE~]';
//
// Refer to the TypeScript documentation at
// https://www.typescriptlang.org/docs/handbook/modules.html#export--and-import--
require
// to understand common workarounds for this limitation of ES6 modules.

/*~ If this module is a UMD module that exposes a global variable 'myFuncLib' when
*~ loaded outside a module loader environment, declare that global here.
*~ Otherwise, delete this declaration.
*/
export as namespace myFuncLib;

/*~ This declaration specifies that the function


*~ is the exported object from the file
*/
export = Greeter;

/*~ This example shows how to have multiple overloads for your function */
declare function Greeter(name: string): Greeter.NamedReturnType;
declare function Greeter(length: number): Greeter.LengthReturnType;

/*~ If you want to expose types from your module as well, you can
*~ place them in this block. Often you will want to describe the
*~ shape of the return type of the function; that type should
*~ be declared in here, as this example shows.
*~
*~ Note that if you decide to include this namespace, the module can be
*~ incorrectly imported as a namespace object, unless
*~ --esModuleInterop is turned on:
*~ import * as x from '[~THE MODULE~]'; // WRONG! DO NOT DO THIS!
*/
declare namespace Greeter {

48
Module: Function

export interface LengthReturnType {


width: number;
height: number;
}
export interface NamedReturnType {
firstName: string;
lastName: string;
}

/*~ If the module also has properties, declare them here. For example,
*~ this declaration says that this code is legal:
*~ import f = require('super-greeter');
*~ console.log(f.defaultName);
*/
export const defaultName: string;
export let defaultLength: number;
}

Go to TOC

49
typescript

For example, when you want to work with JavaScript code which extends another library.

import { greeter } from "super-greeter";

// Normal Greeter API


greeter(2);
greeter("Hello world");

// Now we extend the object with a new function at runtime


import "hyper-super-greeter";
greeter.hyperGreet();

The definition for "super-greeter":

/*~ This example shows how to have multiple overloads for your function */
export interface GreeterFunction {
(name: string): void
(time: number): void
}

/*~ This example shows how to export a function specified by an interface */


export const greeter: GreeterFunction;

We can extend the existing module like the following:

// Type definitions for [~THE LIBRARY NAME~] [~OPTIONAL VERSION NUMBER~]


// Project: [~THE PROJECT NAME~]
// Definitions by: [~YOUR NAME~] <[~A URL FOR YOU~]>

/*~ This is the module plugin template file. You should rename it to index.d.ts
*~ and place it in a folder with the same name as the module.
*~ For example, if you were writing a file for "super-greeter", this
*~ file should be 'super-greeter/index.d.ts'
*/

/*~ On this line, import the module which this module adds to */
import { greeter } from "super-greeter";

/*~ Here, declare the same module as the one you imported above
*~ then we expand the existing declaration of the greeter function
*/
export module "super-greeter" {
export interface GreeterFunction {
/** Greets even better! */
hyperGreet(): void;
}
}

This uses declaration merging

The Impact of ES6 on Module Plugins


Some plugins add or modify top-level exports on existing modules. While this is legal in CommonJS and
other loaders, ES6 modules are considered immutable and this pattern will not be possible. Because
TypeScript is loader-agnostic, there is no compile-time enforcement of this policy, but developers intending
to transition to an ES6 module loader should be aware of this.

50
Module: Plugin

Go to TOC

51
typescript

Comparing JavaScript to an example DTS


Common CommonJS Patterns
A module using CommonJS patterns uses module.exports to describe the exported values. For example,
here is a module which exports a function and a numerical constant:

const maxInterval = 12;

function getArrayLength(arr) {
return arr.length;
}

module.exports = {
getArrayLength,
maxInterval,
};

This can be described by the following .d.ts :

export function getArrayLength(arr: any[]): number;


export const maxInterval: 12;

The TypeScript playground can show you the .d.ts equivalent for JavaScript code. You can try it yourself
here.

The .d.ts syntax intentionally looks like ES Modules syntax. ES Modules was ratified by TC39 in 2019,
while it has been available via transpilers for a long time, however if you have a JavaScript codebase using
ES Modules:

export function getArrayLength(arr) {


return arr.length;
}

This would have the following .d.ts equivalent:

export function getArrayLength(arr: any[]): number;

Default Exports
In CommonJS you can export any value as the default export, for example here is a regular expression
module:

module.exports = /hello( world)?/;

Which can be described by the following .d.ts:

declare const helloWorld: RegExp;


export default helloWorld;

52
Modules .d.ts

Or a number:

module.exports = 3.142;

declare const pi: number;


export default pi;

One style of exporting in CommonJS is to export a function. Because a function is also an object, then extra
fields can be added and are included in the export.

function getArrayLength(arr) {
return arr.length;
}
getArrayLength.maxInterval = 12;

module.exports = getArrayLength;

Which can be described with:

export default function getArrayLength(arr: any[]): number;


export const maxInterval: 12;

Note that using export default in your .d.ts files requires esModuleInterop: true to work. If you can't
have esModuleInterop: true in your project, such as when you're submitting a PR to Definitely Typed,
you'll have to use the export= syntax instead. This older syntax is harder to use but works everywhere.
Here's how the above example would have to be written using export= :

declare function getArrayLength(arr: any[]): number;


declare namespace getArrayLength {
declare const maxInterval: 12;
}

export = getArrayLength;

See Module: Functions for details of how that works, and the Modules reference page.

Handling Many Consuming Import


There are many ways to import a module in modern consuming code:

const fastify = require("fastify");


const { fastify } = require("fastify");
import fastify = require("fastify");
import * as Fastify from "fastify";
import { fastify, FastifyInstance } from "fastify";
import fastify from "fastify";
import fastify, { FastifyInstance } from "fastify";

Covering all of these cases requires the JavaScript code to actually support all of these patterns. To support
many of these patterns, a CommonJS module would need to look something like:

53
typescript

class FastifyInstance {}

function fastify() {
return new FastifyInstance();
}

fastify.FastifyInstance = FastifyInstance;

// Allows for { fastify }


fastify.fastify = fastify;
// Allows for strict ES Module support
fastify.default = fastify;
// Sets the default export
module.exports = fastify;

Types in Modules
You may want to provide a type for JavaScript code which does not exist

function getArrayMetadata(arr) {
return {
length: getArrayLength(arr),
firstObject: arr[0],
};
}

module.exports = {
getArrayMetadata,
};

This can be described with:

export type ArrayMetadata = {


length: number;
firstObject: any | undefined;
};
export function getArrayMetadata(arr: any[]): ArrayMetadata;

This example is a good case for using generics to provide richer type information:

export type ArrayMetadata<ArrType> = {


length: number;
firstObject: ArrType | undefined;
};

export function getArrayMetadata<ArrType>(


arr: ArrType[]
): ArrayMetadata<ArrType>;

Now the type of the array propagates into the ArrayMetadata type.

The types which are exported can then be re-used by consumers of the modules using either import or
import type in TypeScript code or JSDoc imports.

54
Modules .d.ts

Namespaces in Module Code


Trying to describe the runtime relationship of JavaScript code can be tricky. When the ES Module-like syntax
doesn't provide enough tools to describe the exports then you can use namespaces .

For example, you may have complex enough types to describe that you choose to namespace them inside
your .d.ts :

// This represents the JavaScript class which would be available at runtime


export class API {
constructor(baseURL: string);
getInfo(opts: API.InfoRequest): API.InfoResponse;
}

// This namespace is merged with the API class and allows for consumers, and this
file
// to have types which are nested away in their own sections.
declare namespace API {
export interface InfoRequest {
id: string;
}

export interface InfoResponse {


width: number;
height: number;
}
}

To understand how namespaces work in .d.ts files read the .d.ts deep dive.

Optional Global Usage


You can use export as namespace to declare that your module will be available in the global scope in
UMD contexts:

export as namespace moduleName;

Reference Example
To give you an idea of how all these pieces can come together, here is a reference .d.ts to start with
when making a new module

// Type definitions for [~THE LIBRARY NAME~] [~OPTIONAL VERSION NUMBER~]


// Project: [~THE PROJECT NAME~]
// Definitions by: [~YOUR NAME~] <[~A URL FOR YOU~]>

/*~ This is the module template file. You should rename it to index.d.ts
*~ and place it in a folder with the same name as the module.
*~ For example, if you were writing a file for "super-greeter", this
*~ file should be 'super-greeter/index.d.ts'
*/

/*~ If this module is a UMD module that exposes a global variable 'myLib' when
*~ loaded outside a module loader environment, declare that global here.
*~ Otherwise, delete this declaration.
*/

55
typescript

export as namespace myLib;

/*~ If this module exports functions, declare them like so.


*/
export function myFunction(a: string): string;
export function myOtherFunction(a: number): number;

/*~ You can declare types that are available via importing the module */
export interface SomeType {
name: string;
length: number;
extras?: string[];
}

/*~ You can declare properties of the module using const, let, or var */
export const myField: number;

Library file layout


The layout of your declaration files should mirror the layout of the library.

A library can consist of multiple modules, such as

myLib
+---- index.js
+---- foo.js
+---- bar
+---- index.js
+---- baz.js

These could be imported as

var a = require("myLib");
var b = require("myLib/foo");
var c = require("myLib/bar");
var d = require("myLib/bar/baz");

Your declaration files should thus be

@types/myLib
+---- index.d.ts
+---- foo.d.ts
+---- bar
+---- index.d.ts
+---- baz.d.ts

Testing your types


If you are planning on submitting these changes to DefinitelyTyped for everyone to also use, then we rec-
ommend you:

1. Create a new folder in node_modules/@types/[libname]


2. Create an index.d.ts in that folder, and copy the example in
3. See where your usage of the module breaks, and start to fill out the index.d.ts

56
Modules .d.ts

4. When you're happy, clone DefinitelyTyped/DefinitelyTyped and follow the instructions in the
README.

Otherwise

1. Create a new file in the root of your source tree: [libname].d.ts


2. Add declare module "[libname]" { }
3. Add the template inside the braces of the declare module, and see where your usage breaks

Go to TOC

57
typescript

TypeScript began its life as an attempt to bring traditional object-oriented types to JavaScript so that the
programmers at Microsoft could bring traditional object-oriented programs to the web. As it has developed,
TypeScript's type system has evolved to model code written by native JavaScripters. The resulting system is
powerful, interesting and messy.

This introduction is designed for working Haskell or ML programmers who want to learn TypeScript. It de-
scribes how the type system of TypeScript differs from Haskell's type system. It also describes unique fea-
tures of TypeScript's type system that arise from its modelling of JavaScript code.

This introduction does not cover object-oriented programming. In practice, object-oriented programs in
TypeScript are similar to those in other popular languages with OO features.

Prerequisites
In this introduction, I assume you know the following:

How to program in JavaScript, the good parts.


Type syntax of a C-descended language.

If you need to learn the good parts of JavaScript, read JavaScript: The Good Parts. You may be able to skip
the book if you know how to write programs in a call-by-value lexically scoped language with lots of muta-
bility and not much else. R4RS Scheme is a good example.

The C++ Programming Language is a good place to learn about C-style type syntax. Unlike C++, TypeScript
uses postfix types, like so: x: string instead of string x .

Concepts not in Haskell


Built-in types
JavaScript defines 8 built-in types:

Type Explanation

Number a double-precision IEEE 754 floating point.

String an immutable UTF-16 string.

BigInt integers in the arbitrary precision format.

Boolean true and false .

Symbol a unique value usually used as a key.

Null equivalent to the unit type.

Undefined also equivalent to the unit type.

Object similar to records.

See the MDN page for more detail.

58
TypeScript for Functional Programmers

TypeScript has corresponding primitive types for the built-in types:

number
string
bigint
boolean
symbol
null
undefined
object

Other important TypeScript types

Type Explanation

unknown the top type.

never the bottom type.

object literal eg { property: Type }

void a subtype of undefined intended for use as a return type.

T[] mutable arrays, also written Array<T>

[T, T] tuples, which are fixed-length but mutable

(t: T) => U functions

Notes:

1. Function syntax includes parameter names. This is pretty hard to get used to!

let fst: (a: any, b: any) => any = (a, b) => a;

// or more precisely:

let fst: <T, U>(a: T, b: U) => T = (a, b) => a;

2. Object literal type syntax closely mirrors object literal value syntax:

let o: { n: number; xs: object[] } = { n: 1, xs: [] };

3. [T, T] is a subtype of T[] . This is different than Haskell, where tuples are not related to lists.

Boxed types
JavaScript has boxed equivalents of primitive types that contain the methods that programmers associate
with those types. TypeScript reflects this with, for example, the difference between the primitive type num‐
ber and the boxed type Number . The boxed types are rarely needed, since their methods return primitives.

59
typescript

(1).toExponential();
// equivalent to
Number.prototype.toExponential.call(1);

Note that calling a method on a numeric literal requires it to be in parentheses to aid the parser.

Gradual typing
TypeScript uses the type any whenever it can't tell what the type of an expression should be. Compared to
Dynamic , calling any a type is an overstatement. It just turns off the type checker wherever it appears.
For example, you can push any value into an any[] without marking the value in any way:

// with "noImplicitAny": false in tsconfig.json, anys: any[]


const anys = [];
anys.push(1);
anys.push("oh no");
anys.push({ anything: "goes" });

And you can use an expression of type any anywhere:

anys.map(anys[1]); // oh no, "oh no" is not a function

any is contagious, too — if you initialize a variable with an expression of type any , the variable has type
any too.

let sepsis = anys[0] + anys[1]; // this could mean anything

To get an error when TypeScript produces an any , use "noImplicitAny": true , or "strict": true in
tsconfig.json .

Structural typing
Structural typing is a familiar concept to most functional programmers, although Haskell and most MLs are
not structurally typed. Its basic form is pretty simple:

// @strict: false
let o = { x: "hi", extra: 1 }; // ok
let o2: { x: string } = o; // ok

Here, the object literal { x: "hi", extra: 1 } has a matching literal type { x: string, extra: num‐
ber } . That type is assignable to { x: string } since it has all the required properties and those proper-
ties have assignable types. The extra property doesn't prevent assignment, it just makes it a subtype of {
x: string } .

Named types just give a name to a type; for assignability purposes there's no difference between the type
alias One and the interface type Two below. They both have a property p: string . (Type aliases behave
differently from interfaces with respect to recursive definitions and type parameters, however.)

60
TypeScript for Functional Programmers

// @errors: 2322
type One = { p: string };
interface Two {
p: string;
}
class Three {
p = "Hello";
}

let x: One = { p: "hi" };


let two: Two = x;
two = new Three();

Unions
In TypeScript, union types are untagged. In other words, they are not discriminated unions like data in
Haskell. However, you can often discriminate types in a union using built-in tags or other properties.

function start(
arg: string | string[] | (() => string) | { s: string }
): string {
// this is super common in JavaScript
if (typeof arg === "string") {
return commonCase(arg);
} else if (Array.isArray(arg)) {
return arg.map(commonCase).join(",");
} else if (typeof arg === "function") {
return commonCase(arg());
} else {
return commonCase(arg.s);
}

function commonCase(s: string): string {


// finally, just convert a string to another string
return s;
}
}

string , Array and Function have built-in type predicates, conveniently leaving the object type for the
else branch. It is possible, however, to generate unions that are difficult to differentiate at runtime. For
new code, it's best to build only discriminated unions.

The following types have built-in predicates:

Type Predicate

string typeof s === "string"

number typeof n === "number"

bigint typeof m === "bigint"

boolean typeof b === "boolean"

symbol typeof g === "symbol"

undefined typeof undefined === "undefined"

61
typescript

Type Predicate

function typeof f === "function"

array Array.isArray(a)

object typeof o === "object"

Note that functions and arrays are objects at runtime, but have their own predicates.

Intersections
In addition to unions, TypeScript also has intersections:

type Combined = { a: number } & { b: string };


type Conflicting = { a: number } & { a: string };

Combined has two properties, a and b , just as if they had been written as one object literal type.
Intersection and union are recursive in case of conflicts, so Conflicting.a: number & string .

Unit types
Unit types are subtypes of primitive types that contain exactly one primitive value. For example, the string
"foo" has the type "foo" . Since JavaScript has no built-in enums, it is common to use a set of well-
known strings instead. Unions of string literal types allow TypeScript to type this pattern:

declare function pad(s: string, n: number, direction: "left" | "right"): string;


pad("hi", 10, "left");

When needed, the compiler widens — converts to a supertype — the unit type to the primitive type, such as
"foo" to string . This happens when using mutability, which can hamper some uses of mutable
variables:

// @errors: 2345
declare function pad(s: string, n: number, direction: "left" | "right"): string;
// ---cut---
let s = "right";
pad("hi", 10, s); // error: 'string' is not assignable to '"left" | "right"'

Here's how the error happens:

"right": "right"
s: string because "right" widens to string on assignment to a mutable variable.
string is not assignable to "left" | "right"

You can work around this with a type annotation for s , but that in turn prevents assignments to s of vari-
ables that are not of type "left" | "right" .

declare function pad(s: string, n: number, direction: "left" | "right"): string;


// ---cut---
let s: "left" | "right" = "right";
pad("hi", 10, s);

62
TypeScript for Functional Programmers

Concepts similar to Haskell


Contextual typing
TypeScript has some obvious places where it can infer types, like variable declarations:

let s = "I'm a string!";

But it also infers types in a few other places that you may not expect if you've worked with other C-syntax
languages:

declare function map<T, U>(f: (t: T) => U, ts: T[]): U[];


let sns = map((n) => n.toString(), [1, 2, 3]);

Here, n: number in this example also, despite the fact that T and U have not been inferred before the
call. In fact, after [1,2,3] has been used to infer T=number , the return type of n => n.toString() is
used to infer U=string , causing sns to have the type string[] .

Note that inference will work in any order, but intellisense will only work left-to-right, so TypeScript prefers
to declare map with the array first:

declare function map<T, U>(ts: T[], f: (t: T) => U): U[];

Contextual typing also works recursively through object literals, and on unit types that would otherwise be
inferred as string or number . And it can infer return types from context:

declare function run<T>(thunk: (t: T) => void): T;


let i: { inference: string } = run((o) => {
o.inference = "INSERT STATE HERE";
});

The type of o is determined to be { inference: string } because

1. Declaration initializers are contextually typed by the declaration's type: { inference: string } .
2. The return type of a call uses the contextual type for inferences, so the compiler infers that T={ infer‐
ence: string } .
3. Arrow functions use the contextual type to type their parameters, so the compiler gives o: {
inference: string } .

And it does so while you are typing, so that after typing o. , you get completions for the property infer‐
ence , along with any other properties you'd have in a real program. Altogether, this feature can make
TypeScript's inference look a bit like a unifying type inference engine, but it is not.

Type aliases
Type aliases are mere aliases, just like type in Haskell. The compiler will attempt to use the alias name
wherever it was used in the source code, but does not always succeed.

63
typescript

type Size = [number, number];


let x: Size = [101.1, 999.9];

The closest equivalent to newtype is a tagged intersection:

type FString = string & { __compileTimeOnly: any };

An FString is just like a normal string, except that the compiler thinks it has a property named __com‐
pileTimeOnly that doesn't actually exist. This means that FString can still be assigned to string , but
not the other way round.

Discriminated Unions
The closest equivalent to data is a union of types with discriminant properties, normally called discriminat-
ed unions in TypeScript:

type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; x: number }
| { kind: "triangle"; x: number; y: number };

Unlike Haskell, the tag, or discriminant, is just a property in each object type. Each variant has an identical
property with a different unit type. This is still a normal union type; the leading | is an optional part of the
union type syntax. You can discriminate the members of the union using normal JavaScript code:

type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; x: number }
| { kind: "triangle"; x: number; y: number };

function area(s: Shape) {


if (s.kind === "circle") {
return Math.PI * s.radius * s.radius;
} else if (s.kind === "square") {
return s.x * s.x;
} else {
return (s.x * s.y) / 2;
}
}

Note that the return type of area is inferred to be number because TypeScript knows the function is total.
If some variant is not covered, the return type of area will be number | undefined instead.

Also, unlike Haskell, common properties show up in any union, so you can usefully discriminate multiple
members of the union:

type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; x: number }
| { kind: "triangle"; x: number; y: number };
// ---cut---
function height(s: Shape) {
if (s.kind === "circle") {
return 2 * s.radius;

64
TypeScript for Functional Programmers

} else {
// s.kind: "square" | "triangle"
return s.x;
}
}

Type Parameters
Like most C-descended languages, TypeScript requires declaration of type parameters:

function liftArray<T>(t: T): Array<T> {


return [t];
}

There is no case requirement, but type parameters are conventionally single uppercase letters. Type para-
meters can also be constrained to a type, which behaves a bit like type class constraints:

function firstish<T extends { length: number }>(t1: T, t2: T): T {


return t1.length > t2.length ? t1 : t2;
}

TypeScript can usually infer type arguments from a call based on the type of the arguments, so type argu-
ments are usually not needed.

Because TypeScript is structural, it doesn't need type parameters as much as nominal systems. Specifically,
they are not needed to make a function polymorphic. Type parameters should only be used to propagate
type information, such as constraining parameters to be the same type:

function length<T extends ArrayLike<unknown>>(t: T): number {}

function length(t: ArrayLike<unknown>): number {}

In the first length , T is not necessary; notice that it's only referenced once, so it's not being used to con-
strain the type of the return value or other parameters.

Higher-kinded types
TypeScript does not have higher kinded types, so the following is not legal:

function length<T extends ArrayLike<unknown>, U>(m: T<U>) {}

Point-free programming
Point-free programming — heavy use of currying and function composition — is possible in JavaScript, but
can be verbose. In TypeScript, type inference often fails for point-free programs, so you'll end up specifying
type parameters instead of value parameters. The result is so verbose that it's usually better to avoid point-
free programming.

65
typescript

Module system
JavaScript's modern module syntax is a bit like Haskell's, except that any file with import or export is
implicitly a module:

import { value, Type } from "npm-package";


import { other, Types } from "./local-package";
import * as prefix from "../lib/third-package";

You can also import commonjs modules — modules written using node.js' module system:

import f = require("single-function-package");

You can export with an export list:

export { f };

function f() {
return g();
}
function g() {} // g is not exported

Or by marking each export individually:

export function f { return g() }


function g() { }

The latter style is more common but both are allowed, even in the same file.

readonly and const


In JavaScript, mutability is the default, although it allows variable declarations with const to declare that
the reference is immutable. The referent is still mutable:

const a = [1, 2, 3];


a.push(102); // ):
a[0] = 101; // D:

TypeScript additionally has a readonly modifier for properties.

interface Rx {
readonly x: number;
}
let rx: Rx = { x: 1 };
rx.x = 12; // error

It also ships with a mapped type Readonly<T> that makes all properties readonly :

interface X {
x: number;
}
let rx: Readonly<X> = { x: 1 };
rx.x = 12; // error

66
TypeScript for Functional Programmers

And it has a specific ReadonlyArray<T> type that removes side-affecting methods and prevents writing to
indices of the array, as well as special syntax for this type:

let a: ReadonlyArray<number> = [1, 2, 3];


let b: readonly number[] = [1, 2, 3];
a.push(102); // error
b[0] = 101; // error

You can also use a const-assertion, which operates on arrays and object literals:

let a = [1, 2, 3] as const;


a.push(102); // error
a[0] = 101; // error

However, none of these options are the default, so they are not consistently used in TypeScript code.

Next Steps
This doc is a high level overview of the syntax and types you would use in everyday code. From here you
should:

Read the full Handbook from start to finish (30m)


Explore the Playground examples

Go to TOC

67
typescript

TypeScript stands in an unusual relationship to JavaScript. TypeScript offers all of JavaScript's features, and
an additional layer on top of these: TypeScript's type system.

For example, JavaScript provides language primitives like string and number , but it doesn't check that
you've consistently assigned these. TypeScript does.

This means that your existing working JavaScript code is also TypeScript code. The main benefit of
TypeScript is that it can highlight unexpected behavior in your code, lowering the chance of bugs.

This tutorial provides a brief overview of TypeScript, focusing on its type system.

Types by Inference
TypeScript knows the JavaScript language and will generate types for you in many cases. For example in
creating a variable and assigning it to a particular value, TypeScript will use the value as its type.

let helloWorld = "Hello World";


// ^?

By understanding how JavaScript works, TypeScript can build a type-system that accepts JavaScript code
but has types. This offers a type-system without needing to add extra characters to make types explicit in
your code. That's how TypeScript knows that helloWorld is a string in the above example.

You may have written JavaScript in Visual Studio Code, and had editor auto-completion. Visual Studio Code
uses TypeScript under the hood to make it easier to work with JavaScript.

Defining Types
You can use a wide variety of design patterns in JavaScript. However, some design patterns make it difficult
for types to be inferred automatically (for example, patterns that use dynamic programming). To cover
these cases, TypeScript supports an extension of the JavaScript language, which offers places for you to tell
TypeScript what the types should be.

For example, to create an object with an inferred type which includes name: string and id: number ,
you can write:

const user = {
name: "Hayes",
id: 0,
};

You can explicitly describe this object's shape using an interface declaration:

interface User {
name: string;
id: number;
}

68
TypeScript for JavaScript Programmers

You can then declare that a JavaScript object conforms to the shape of your new interface by using syn-
tax like : TypeName after a variable declaration:

interface User {
name: string;
id: number;
}
// ---cut---
const user: User = {
name: "Hayes",
id: 0,
};

If you provide an object that doesn't match the interface you have provided, TypeScript will warn you:

// @errors: 2322
interface User {
name: string;
id: number;
}

const user: User = {


username: "Hayes",
id: 0,
};

Since JavaScript supports classes and object-oriented programming, so does TypeScript. You can use an in-
terface declaration with classes:

interface User {
name: string;
id: number;
}

class UserAccount {
name: string;
id: number;

constructor(name: string, id: number) {


this.name = name;
this.id = id;
}
}

const user: User = new UserAccount("Murphy", 1);

You can use interfaces to annotate parameters and return values to functions:

// @noErrors
interface User {
name: string;
id: number;
}
// ---cut---
function getAdminUser(): User {
//...
}

69
typescript

function deleteUser(user: User) {


// ...
}

There is already a small set of primitive types available in JavaScript: boolean , bigint , null , number ,
string , symbol , and undefined , which you can use in an interface. TypeScript extends this list with a
few more, such as any (allow anything), unknown (ensure someone using this type declares what the type
is), never (it's not possible that this type could happen), and void (a function which returns undefined
or has no return value).

You'll see that there are two syntaxes for building types: Interfaces and Types. You should prefer inter‐
face . Use type when you need specific features.

Composing Types
With TypeScript, you can create complex types by combining simple ones. There are two popular ways to do
so: with unions, and with generics.

Unions
With a union, you can declare that a type could be one of many types. For example, you can describe a
boolean type as being either true or false :

type MyBool = true | false;

Note: If you hover over MyBool above, you'll see that it is classed as boolean . That's a property of the
Structural Type System. More on this below.

A popular use-case for union types is to describe the set of string or number literals that a value is al-
lowed to be:

type WindowStates = "open" | "closed" | "minimized";


type LockStates = "locked" | "unlocked";
type PositiveOddNumbersUnderTen = 1 | 3 | 5 | 7 | 9;

Unions provide a way to handle different types too. For example, you may have a function that takes an
array or a string :

function getLength(obj: string | string[]) {


return obj.length;
}

To learn the type of a variable, use typeof :

Type Predicate

string typeof s === "string"

number typeof n === "number"

boolean typeof b === "boolean"

70
TypeScript for JavaScript Programmers

Type Predicate

undefined typeof undefined === "undefined"

function typeof f === "function"

array Array.isArray(a)

For example, you can make a function return different values depending on whether it is passed a string or
an array:

function wrapInArray(obj: string | string[]) {


if (typeof obj === "string") {
return [obj];
// ^?
}
return obj;
}

Generics
Generics provide variables to types. A common example is an array. An array without generics could contain
anything. An array with generics can describe the values that the array contains.

type StringArray = Array<string>;


type NumberArray = Array<number>;
type ObjectWithNameArray = Array<{ name: string }>;

You can declare your own types that use generics:

// @errors: 2345
interface Backpack<Type> {
add: (obj: Type) => void;
get: () => Type;
}

// This line is a shortcut to tell TypeScript there is a


// constant called `backpack`, and to not worry about where it came from.
declare const backpack: Backpack<string>;

// object is a string, because we declared it above as the variable part of


Backpack.
const object = backpack.get();

// Since the backpack variable is a string, you can't pass a number to the add
function.
backpack.add(23);

Structural Type System


One of TypeScript's core principles is that type checking focuses on the shape that values have. This is
sometimes called "duck typing" or "structural typing".

In a structural type system, if two objects have the same shape, they are considered to be of the same
type.

71
typescript

interface Point {
x: number;
y: number;
}

function logPoint(p: Point) {


console.log(`${p.x}, ${p.y}`);
}

// logs "12, 26"


const point = { x: 12, y: 26 };
logPoint(point);

The point variable is never declared to be a Point type. However, TypeScript compares the shape of
point to the shape of Point in the type-check. They have the same shape, so the code passes.

The shape-matching only requires a subset of the object's fields to match.

// @errors: 2345
interface Point {
x: number;
y: number;
}

function logPoint(p: Point) {


console.log(`${p.x}, ${p.y}`);
}
// ---cut---
const point3 = { x: 12, y: 26, z: 89 };
logPoint(point3); // logs "12, 26"

const rect = { x: 33, y: 3, width: 30, height: 80 };


logPoint(rect); // logs "33, 3"

const color = { hex: "#187ABF" };


logPoint(color);

There is no difference between how classes and objects conform to shapes:

// @errors: 2345
interface Point {
x: number;
y: number;
}

function logPoint(p: Point) {


console.log(`${p.x}, ${p.y}`);
}
// ---cut---
class VirtualPoint {
x: number;
y: number;

constructor(x: number, y: number) {


this.x = x;
this.y = y;
}
}

72
TypeScript for JavaScript Programmers

const newVPoint = new VirtualPoint(13, 56);


logPoint(newVPoint); // logs "13, 56"

If the object or class has all the required properties, TypeScript will say they match, regardless of the imple-
mentation details.

Next Steps
This was a brief overview of the syntax and tools used in everyday TypeScript. From here, you can:

Read the full Handbook from start to finish


Explore the Playground examples

Go to TOC

73
typescript

TypeScript is a popular choice for programmers accustomed to other languages with static typing, such as
C# and Java.

TypeScript's type system offers many of the same benefits, such as better code completion, earlier detec-
tion of errors, and clearer communication between parts of your program. While TypeScript provides many
familiar features for these developers, it's worth stepping back to see how JavaScript (and therefore
TypeScript) differ from traditional OOP languages. Understanding these differences will help you write better
JavaScript code, and avoid common pitfalls that programmers who go straight from C#/Java to TypeScript
may fall in to.

Co-learning JavaScript
If you're familiar with JavaScript already but are primarily a Java or C# programmer, this introductory page
can help explain some of the common misconceptions and pitfalls you might be susceptible to. Some of the
ways that TypeScript models types are quite different from Java or C#, and it's important to keep these in
mind when learning TypeScript.

If you're a Java or C# programmer that is new to JavaScript in general, we recommend learning a little bit
of JavaScript without types first to understand JavaScript's runtime behaviors. Because TypeScript doesn't
change how your code runs, you'll still have to learn how JavaScript works in order to write code that actu-
ally does something!

It's important to remember that TypeScript uses the same runtime as JavaScript, so any resources about
how to accomplish specific runtime behavior (converting a string to a number, displaying an alert, writing a
file to disk, etc.) will always apply equally well to TypeScript programs. Don't limit yourself to TypeScript-
specific resources!

Rethinking the Class


C# and Java are what we might call mandatory OOP languages. In these languages, the class is the basic
unit of code organization, and also the basic container of all data and behavior at runtime. Forcing all func-
tionality and data to be held in classes can be a good domain model for some problems, but not every do-
main needs to be represented this way.

Free Functions and Data


In JavaScript, functions can live anywhere, and data can be passed around freely without being inside a
pre-defined class or struct . This flexibility is extremely powerful. "Free" functions (those not associated
with a class) working over data without an implied OOP hierarchy tends to be the preferred model for writ-
ing programs in JavaScript.

Static Classes
Additionally, certain constructs from C# and Java such as singletons and static classes are unnecessary in
TypeScript.

74
TypeScript for Java/C# Programmers

OOP in TypeScript
That said, you can still use classes if you like! Some problems are well-suited to being solved by a tradition-
al OOP hierarchy, and TypeScript's support for JavaScript classes will make these models even more power-
ful. TypeScript supports many common patterns such as implementing interfaces, inheritance, and static
methods.

We'll cover classes later in this guide.

Rethinking Types
TypeScript's understanding of a type is actually quite different from C# or Java's. Let's explore some
differences.

Nominal Reified Type Systems


In C# or Java, any given value or object has one exact type - either null , a primitive, or a known class
type. We can call methods like value.GetType() or value.getClass() to query the exact type at run-
time. The definition of this type will reside in a class somewhere with some name, and we can't use two
classes with similar shapes in lieu of each other unless there's an explicit inheritance relationship or com-
monly-implemented interface.

These aspects describe a reified, nominal type system. The types we wrote in the code are present at run-
time, and the types are related via their declarations, not their structures.

Types as Sets
In C# or Java, it's meaningful to think of a one-to-one correspondence between runtime types and their
compile-time declarations.

In TypeScript, it's better to think of a type as a set of values that share something in common. Because
types are just sets, a particular value can belong to many sets at the same time.

Once you start thinking of types as sets, certain operations become very natural. For example, in C#, it's
awkward to pass around a value that is either a string or int , because there isn't a single type that rep-
resents this sort of value.

In TypeScript, this becomes very natural once you realize that every type is just a set. How do you describe
a value that either belongs in the string set or the number set? It simply belongs to the union of those
sets: string | number .

TypeScript provides a number of mechanisms to work with types in a set-theoretic way, and you'll find them
more intuitive if you think of types as sets.

75
typescript

Erased Structural Types


In TypeScript, objects are not of a single exact type. For example, if we construct an object that satisfies an
interface, we can use that object where that interface is expected even though there was no declarative re-
lationship between the two.

interface Pointlike {
x: number;
y: number;
}
interface Named {
name: string;
}

function logPoint(point: Pointlike) {


console.log("x = " + point.x + ", y = " + point.y);
}

function logName(x: Named) {


console.log("Hello, " + x.name);
}

const obj = {
x: 0,
y: 0,
name: "Origin",
};

logPoint(obj);
logName(obj);

TypeScript's type system is structural, not nominal: We can use obj as a Pointlike because it has x
and y properties that are both numbers. The relationships between types are determined by the properties
they contain, not whether they were declared with some particular relationship.

TypeScript's type system is also not reified: There's nothing at runtime that will tell us that obj is
Pointlike . In fact, the Pointlike type is not present in any form at runtime.

Going back to the idea of types as sets, we can think of obj as being a member of both the Pointlike
set of values and the Named set of values.

Consequences of Structural Typing


OOP programmers are often surprised by two particular aspects of structural typing.

Empty Types

The first is that the empty type seems to defy expectation:

class Empty {}

function fn(arg: Empty) {


// do something?
}

76
TypeScript for Java/C# Programmers

// No error, but this isn't an 'Empty' ?


fn({ k: 10 });

TypeScript determines if the call to fn here is valid by seeing if the provided argument is a valid Empty . It
does so by examining the structure of { k: 10 } and class Empty { } . We can see that { k: 10 }
has all of the properties that Empty does, because Empty has no properties. Therefore, this is a valid call!

This may seem surprising, but it's ultimately a very similar relationship to one enforced in nominal OOP lan-
guages. A subclass cannot remove a property of its base class, because doing so would destroy the natural
subtype relationship between the derived class and its base. Structural type systems simply identify this re-
lationship implicitly by describing subtypes in terms of having properties of compatible types.

Identical Types

Another frequent source of surprise comes with identical types:

class Car {
drive() {
// hit the gas
}
}
class Golfer {
drive() {
// hit the ball far
}
}

// No error?
let w: Car = new Golfer();

Again, this isn't an error because the structures of these classes are the same. While this may seem like a
potential source of confusion, in practice, identical classes that shouldn't be related are not common.

We'll learn more about how classes relate to each other in the Classes chapter.

Reflection
OOP programmers are accustomed to being able to query the type of any value, even a generic one:

// C#
static void LogType<T>() {
Console.WriteLine(typeof(T).Name);
}

Because TypeScript's type system is fully erased, information about e.g. the instantiation of a generic type
parameter is not available at runtime.

JavaScript does have some limited primitives like typeof and instanceof , but remember that these op-
erators are still working on the values as they exist in the type-erased output code. For example, typeof
(new Car()) will be "object" , not Car or "Car" .

77
typescript

Next Steps
This was a brief overview of the syntax and tools used in everyday TypeScript. From here, you can:

Read the full Handbook from start to finish (30m)


Explore the Playground examples

Go to TOC

78
TypeScript for the New Programmer

Congratulations on choosing TypeScript as one of your first languages — you're already making good
decisions!

You've probably already heard that TypeScript is a "flavor" or "variant" of JavaScript. The relationship be-
tween TypeScript (TS) and JavaScript (JS) is rather unique among modern programming languages, so
learning more about this relationship will help you understand how TypeScript adds to JavaScript.

What is JavaScript? A Brief History


JavaScript (also known as ECMAScript) started its life as a simple scripting language for browsers. At the
time it was invented, it was expected to be used for short snippets of code embedded in a web page — writ-
ing more than a few dozen lines of code would have been somewhat unusual. Due to this, early web
browsers executed such code pretty slowly. Over time, though, JS became more and more popular, and web
developers started using it to create interactive experiences.

Web browser developers responded to this increased JS usage by optimizing their execution engines (dy-
namic compilation) and extending what could be done with it (adding APIs), which in turn made web devel-
opers use it even more. On modern websites, your browser is frequently running applications that span
hundreds of thousands of lines of code. This is long and gradual growth of "the web", starting as a simple
network of static pages, and evolving into a platform for rich applications of all kinds.

More than this, JS has become popular enough to be used outside the context of browsers, such as imple-
menting JS servers using node.js. The "run anywhere" nature of JS makes it an attractive choice for cross-
platform development. There are many developers these days that use only JavaScript to program their en-
tire stack!

To summarize, we have a language that was designed for quick uses, and then grew to a full-fledged tool to
write applications with millions of lines. Every language has its own quirks — oddities and surprises, and
JavaScript's humble beginning makes it have many of these. Some examples:

JavaScript's equality operator ( == ) coerces its arguments, leading to unexpected behavior:

if ("" == 0) {
// It is! But why??
}
if (1 < x < 3) {
// True for *any* value of x!
}

JavaScript also allows accessing properties which aren't present:

const obj = { width: 10, height: 15 };


// Why is this NaN? Spelling is hard!
const area = obj.width * obj.heigth;

79
typescript

Most programming languages would throw an error when these sorts of errors occur, some would do so dur-
ing compilation — before any code is running. When writing small programs, such quirks are annoying but
manageable; when writing applications with hundreds or thousands of lines of code, these constant surpris-
es are a serious problem.

TypeScript: A Static Type Checker


We said earlier that some languages wouldn't allow those buggy programs to run at all. Detecting errors in
code without running it is referred to as static checking. Determining what's an error and what's not based
on the kinds of values being operated on is known as static type checking.

TypeScript checks a program for errors before execution, and does so based on the kinds of values, it's a
static type checker. For example, the last example above has an error because of the type of obj . Here's
the error TypeScript found:

// @errors: 2551
const obj = { width: 10, height: 15 };
const area = obj.width * obj.heigth;

A Typed Superset of JavaScript


How does TypeScript relate to JavaScript, though?

Syntax

TypeScript is a language that is a superset of JavaScript: JS syntax is therefore legal TS. Syntax refers to
the way we write text to form a program. For example, this code has a syntax error because it's missing a
):

// @errors: 1005
let a = (4

TypeScript doesn't consider any JavaScript code to be an error because of its syntax. This means you can
take any working JavaScript code and put it in a TypeScript file without worrying about exactly how it is
written.

Types

However, TypeScript is a typed superset, meaning that it adds rules about how different kinds of values can
be used. The earlier error about obj.heigth was not a syntax error: it is an error of using some kind of
value (a type) in an incorrect way.

As another example, this is JavaScript code that you can run in your browser, and it will log a value:

console.log(4 / []);

This syntactically-legal program logs Infinity . TypeScript, though, considers division of number by an ar-
ray to be a nonsensical operation, and will issue an error:

80
TypeScript for the New Programmer

// @errors: 2363
console.log(4 / []);

It's possible you really did intend to divide a number by an array, perhaps just to see what happens, but
most of the time, though, this is a programming mistake. TypeScript's type checker is designed to allow
correct programs through while still catching as many common errors as possible. (Later, we'll learn about
settings you can use to configure how strictly TypeScript checks your code.)

If you move some code from a JavaScript file to a TypeScript file, you might see type errors depending on
how the code is written. These may be legitimate problems with the code, or TypeScript being overly con-
servative. Throughout this guide we'll demonstrate how to add various TypeScript syntax to eliminate such
errors.

Runtime Behavior

TypeScript is also a programming language that preserves the runtime behavior of JavaScript. For example,
dividing by zero in JavaScript produces Infinity instead of throwing a runtime exception. As a principle,
TypeScript never changes the runtime behavior of JavaScript code.

This means that if you move code from JavaScript to TypeScript, it is guaranteed to run the same way,
even if TypeScript thinks that the code has type errors.

Keeping the same runtime behavior as JavaScript is a foundational promise of TypeScript because it means
you can easily transition between the two languages without worrying about subtle differences that might
make your program stop working.

Erased Types

Roughly speaking, once TypeScript's compiler is done with checking your code, it erases the types to pro-
duce the resulting "compiled" code. This means that once your code is compiled, the resulting plain JS code
has no type information.

This also means that TypeScript never changes the behavior of your program based on the types it inferred.
The bottom line is that while you might see type errors during compilation, the type system itself has no
bearing on how your program works when it runs.

Finally, TypeScript doesn't provide any additional runtime libraries. Your programs will use the same stan-
dard library (or external libraries) as JavaScript programs, so there's no additional TypeScript-specific
framework to learn.

Learning JavaScript and TypeScript


We frequently see the question "Should I learn JavaScript or TypeScript?".

The answer is that you can't learn TypeScript without learning JavaScript! TypeScript shares syntax and run-
time behavior with JavaScript, so anything you learn about JavaScript is helping you learn TypeScript at the
same time.

81
typescript

There are many, many resources available for programmers to learn JavaScript; you should not ignore
these resources if you're writing TypeScript. For example, there are about 20 times more StackOverflow
questions tagged javascript than typescript , but all of the javascript questions also apply to
TypeScript.

If you find yourself searching for something like "how to sort a list in TypeScript", remember: TypeScript is
JavaScript's runtime with a compile-time type checker. The way you sort a list in TypeScript is the
same way you do so in JavaScript. If you find a resource that uses TypeScript directly, that's great too, but
don't limit yourself to thinking you need TypeScript-specific answers for everyday questions about how to
accomplish runtime tasks.

Next Steps
This was a brief overview of the syntax and tools used in everyday TypeScript. From here, you can:

Learn some of the JavaScript fundamentals, we recommend either:

Microsoft's JavaScript Resources or


JavaScript guide at the Mozilla Web Docs

Continue to TypeScript for JavaScript Programmers

Read the full Handbook from start to finish (30m)

Explore the Playground examples

Go to TOC

82
Basic Types

For programs to be useful, we need to be able to work with some of the simplest units of data: numbers,
strings, structures, boolean values, and the like. In TypeScript, we support the same types as you would ex-
pect in JavaScript, with an extra enumeration type thrown in to help things along.

Boolean
The most basic datatype is the simple true/false value, which JavaScript and TypeScript call a boolean
value.

let isDone: boolean = false;

Number
As in JavaScript, all numbers in TypeScript are either floating point values or BigIntegers. These floating
point numbers get the type number , while BigIntegers get the type bigint . In addition to hexadecimal
and decimal literals, TypeScript also supports binary and octal literals introduced in ECMAScript 2015.

// @target: ES2020
let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;
let big: bigint = 100n;

String
Another fundamental part of creating programs in JavaScript for webpages and servers alike is working with
textual data. As in other languages, we use the type string to refer to these textual datatypes. Just like
JavaScript, TypeScript also uses double quotes ( " ) or single quotes ( ' ) to surround string data.

let color: string = "blue";


// prettier-ignore
color = 'red';

You can also use template strings, which can span multiple lines and have embedded expressions. These
strings are surrounded by the backtick/backquote ( ` ) character, and embedded expressions are of the
form ${ expr } .

let fullName: string = `Bob Bobbington`;


let age: number = 37;
let sentence: string = `Hello, my name is ${fullName}.

I'll be ${age + 1} years old next month.`;

This is equivalent to declaring sentence like so:

let fullName: string = `Bob Bobbington`;


let age: number = 37;
// ---cut---
let sentence: string =

83
typescript

"Hello, my name is " +


fullName +
".\n\n" +
"I'll be " +
(age + 1) +
" years old next month.";

Array
TypeScript, like JavaScript, allows you to work with arrays of values. Array types can be written in one of
two ways. In the first, you use the type of the elements followed by [] to denote an array of that element
type:

let list: number[] = [1, 2, 3];

The second way uses a generic array type, Array<elemType> :

let list: Array<number> = [1, 2, 3];

Tuple
Tuple types allow you to express an array with a fixed number of elements whose types are known, but
need not be the same. For example, you may want to represent a value as a pair of a string and a num‐
ber :

// @errors: 2322
// Declare a tuple type
let x: [string, number];
// Initialize it
x = ["hello", 10]; // OK
// Initialize it incorrectly
x = [10, "hello"]; // Error

When accessing an element with a known index, the correct type is retrieved:

// @errors: 2339
let x: [string, number];
x = ["hello", 10]; // OK
/// ---cut---
// OK
console.log(x[0].substring(1));

console.log(x[1].substring(1));

Accessing an element outside the set of known indices fails with an error:

// @errors: 2493 2532 2322


let x: [string, number];
x = ["hello", 10]; // OK
/// ---cut---
x[3] = "world";

console.log(x[5].toString());

84
Basic Types

Enum
A helpful addition to the standard set of datatypes from JavaScript is the enum . As in languages like C#, an
enum is a way of giving more friendly names to sets of numeric values.

enum Color {
Red,
Green,
Blue,
}
let c: Color = Color.Green;

By default, enums begin numbering their members starting at 0 . You can change this by manually setting
the value of one of its members. For example, we can start the previous example at 1 instead of 0 :

enum Color {
Red = 1,
Green,
Blue,
}
let c: Color = Color.Green;

Or, even manually set all the values in the enum:

enum Color {
Red = 1,
Green = 2,
Blue = 4,
}
let c: Color = Color.Green;

A handy feature of enums is that you can also go from a numeric value to the name of that value in the
enum. For example, if we had the value 2 but weren't sure what that mapped to in the Color enum
above, we could look up the corresponding name:

enum Color {
Red = 1,
Green,
Blue,
}
let colorName: string = Color[2];

// Displays 'Green'
console.log(colorName);

Unknown
We may need to describe the type of variables that we do not know when we are writing an application.
These values may come from dynamic content – e.g. from the user – or we may want to intentionally ac-
cept all values in our API. In these cases, we want to provide a type that tells the compiler and future read-
ers that this variable could be anything, so we give it the unknown type.

85
typescript

let notSure: unknown = 4;


notSure = "maybe a string instead";

// OK, definitely a boolean


notSure = false;

If you have a variable with an unknown type, you can narrow it to something more specific by doing type‐
of checks, comparison checks, or more advanced type guards that will be discussed in a later chapter:

// @errors: 2322 2322 2322


declare const maybe: unknown;
// 'maybe' could be a string, object, boolean, undefined, or other types
const aNumber: number = maybe;

if (maybe === true) {


// TypeScript knows that maybe is a boolean now
const aBoolean: boolean = maybe;
// So, it cannot be a string
const aString: string = maybe;
}

if (typeof maybe === "string") {


// TypeScript knows that maybe is a string
const aString: string = maybe;
// So, it cannot be a boolean
const aBoolean: boolean = maybe;
}

Any
In some situations, not all type information is available or its declaration would take an inappropriate
amount of effort. These may occur for values from code that has been written without TypeScript or a 3rd
party library. In these cases, we might want to opt-out of type checking. To do so, we label these values
with the any type:

declare function getValue(key: string): any;


// OK, return value of 'getValue' is not checked
const str: string = getValue("myString");

The any type is a powerful way to work with existing JavaScript, allowing you to gradually opt-in and opt-
out of type checking during compilation.

Unlike unknown , variables of type any allow you to access arbitrary properties, even ones that don't exist.
These properties include functions and TypeScript will not check their existence or type:

// @errors: 2571
let looselyTyped: any = 4;
// OK, ifItExists might exist at runtime
looselyTyped.ifItExists();
// OK, toFixed exists (but the compiler doesn't check)
looselyTyped.toFixed();

let strictlyTyped: unknown = 4;


strictlyTyped.toFixed();

86
Basic Types

The any will continue to propagate through your objects:

let looselyTyped: any = {};


let d = looselyTyped.a.b.c.d;
// ^?

After all, remember that all the convenience of any comes at the cost of losing type safety. Type safety is
one of the main motivations for using TypeScript and you should try to avoid using any when not
necessary.

Void
void is a little like the opposite of any : the absence of having any type at all. You may commonly see this
as the return type of functions that do not return a value:

function warnUser(): void {


console.log("This is my warning message");
}

Declaring variables of type void is not useful because you can only assign null (only if strictNull‐
Checks is not specified, see next section) or undefined to them:

// @strict: false
let unusable: void = undefined;
// OK if `--strictNullChecks` is not given
unusable = null;

Null and Undefined


In TypeScript, both undefined and null actually have their types named undefined and null respec-
tively. Much like void , they're not extremely useful on their own:

// Not much else we can assign to these variables!


let u: undefined = undefined;
let n: null = null;

By default null and undefined are subtypes of all other types. That means you can assign null and
undefined to something like number .

However, when using the strictNullChecks flag, null and undefined are only assignable to unknown ,
any and their respective types (the one exception being that undefined is also assignable to void ). This
helps avoid many common errors. In cases where you want to pass in either a string or null or unde‐
fined , you can use the union type string | null | undefined .

Union types are an advanced topic that we'll cover in a later chapter.

87
typescript

As a note: we encourage the use of strictNullChecks when possible, but for the purposes of this
handbook, we will assume it is turned off.

Never
The never type represents the type of values that never occur. For instance, never is the return type for
a function expression or an arrow function expression that always throws an exception or one that never re-
turns. Variables also acquire the type never when narrowed by any type guards that can never be true.

The never type is a subtype of, and assignable to, every type; however, no type is a subtype of, or as-
signable to, never (except never itself). Even any isn't assignable to never .

Some examples of functions returning never :

// Function returning never must not have a reachable end point


function error(message: string): never {
throw new Error(message);
}

// Inferred return type is never


function fail() {
return error("Something failed");
}

// Function returning never must not have a reachable end point


function infiniteLoop(): never {
while (true) {}
}

Object
object is a type that represents the non-primitive type, i.e. anything that is not number , string , bool‐
ean , bigint , symbol , null , or undefined .

With object type, APIs like Object.create can be better represented. For example:

// @errors: 2345
declare function create(o: object | null): void;

// OK
create({ prop: 0 });
create(null);
create(undefined); // with `--strictNullChecks` flag enabled, undefined is not a
subtype of null

create(42);
create("string");
create(false);

Generally, you won't need to use this.

88
Basic Types

Type assertions
Sometimes you'll end up in a situation where you'll know more about a value than TypeScript does. Usually,
this will happen when you know the type of some entity could be more specific than its current type.

Type assertions are a way to tell the compiler "trust me, I know what I'm doing." A type assertion is like a
type cast in other languages, but it performs no special checking or restructuring of data. It has no runtime
impact and is used purely by the compiler. TypeScript assumes that you, the programmer, have performed
any special checks that you need.

Type assertions have two forms.

One is the as -syntax:

let someValue: unknown = "this is a string";

let strLength: number = (someValue as string).length;

The other version is the "angle-bracket" syntax:

let someValue: unknown = "this is a string";

let strLength: number = (<string>someValue).length;

The two samples are equivalent. Using one over the other is mostly a choice of preference; however, when
using TypeScript with JSX, only as -style assertions are allowed.

A note about let


You may have noticed that so far, we've been using the let keyword instead of JavaScript's var keyword
which you might be more familiar with. The let keyword is actually a newer JavaScript construct that
TypeScript makes available. You can read in the Handbook Reference on Variable Declarations more about
how let and const fix a lot of the problems with var .

About Number , String , Boolean , Symbol and Object


It can be tempting to think that the types Number , String , Boolean , Symbol , or Object are the same
as the lowercase versions recommended above. These types do not refer to the language primitives howev-
er, and almost never should be used as a type.

// @errors: 2339
function reverse(s: String): String {
return s.split("").reverse().join("");
}

reverse("hello world");

Instead, use the types number , string , boolean , object and symbol .

89
typescript

function reverse(s: string): string {


return s.split("").reverse().join("");
}

reverse("hello world");

Go to TOC

90
Classes

Traditional JavaScript uses functions and prototype-based inheritance to build up reusable components, but
this may feel a bit awkward to programmers more comfortable with an object-oriented approach, where
classes inherit functionality and objects are built from these classes. Starting with ECMAScript 2015, also
known as ECMAScript 6, JavaScript programmers can build their applications using this object-oriented
class-based approach. In TypeScript, we allow developers to use these techniques now, and compile them
down to JavaScript that works across all major browsers and platforms, without having to wait for the next
version of JavaScript.

Classes
Let's take a look at a simple class-based example:

class Greeter {
greeting: string;

constructor(message: string) {
this.greeting = message;
}

greet() {
return "Hello, " + this.greeting;
}
}

let greeter = new Greeter("world");

The syntax should look familiar if you've used C# or Java before. We declare a new class Greeter . This
class has three members: a property called greeting , a constructor, and a method greet .

You'll notice that in the class when we refer to one of the members of the class we prepend this. . This
denotes that it's a member access.

In the last line we construct an instance of the Greeter class using new . This calls into the constructor we
defined earlier, creating a new object with the Greeter shape, and running the constructor to initialize it.

Inheritance
In TypeScript, we can use common object-oriented patterns. One of the most fundamental patterns in class-
based programming is being able to extend existing classes to create new ones using inheritance.

Let's take a look at an example:

class Animal {
move(distanceInMeters: number = 0) {
console.log(`Animal moved ${distanceInMeters}m.`);
}
}

class Dog extends Animal {


bark() {
console.log("Woof! Woof!");
}

91
typescript

const dog = new Dog();


dog.bark();
dog.move(10);
dog.bark();

This example shows the most basic inheritance feature: classes inherit properties and methods from base
classes. Here, Dog is a derived class that derives from the Animal base class using the extends
keyword. Derived classes are often called subclasses, and base classes are often called superclasses.

Because Dog extends the functionality from Animal , we were able to create an instance of Dog that could
both bark() and move() .

Let's now look at a more complex example.

class Animal {
name: string;
constructor(theName: string) {
this.name = theName;
}
move(distanceInMeters: number = 0) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}

class Snake extends Animal {


constructor(name: string) {
super(name);
}
move(distanceInMeters = 5) {
console.log("Slithering...");
super.move(distanceInMeters);
}
}

class Horse extends Animal {


constructor(name: string) {
super(name);
}
move(distanceInMeters = 45) {
console.log("Galloping...");
super.move(distanceInMeters);
}
}

let sam = new Snake("Sammy the Python");


let tom: Animal = new Horse("Tommy the Palomino");

sam.move();
tom.move(34);

This example covers a few other features we didn't previously mention. Again, we see the extends key-
words used to create two new subclasses of Animal : Horse and Snake .

92
Classes

One difference from the prior example is that each derived class that contains a constructor function must
call super() which will execute the constructor of the base class. What's more, before we ever access a
property on this in a constructor body, we have to call super() . This is an important rule that TypeScript
will enforce.

The example also shows how to override methods in the base class with methods that are specialized for
the subclass. Here both Snake and Horse create a move method that overrides the move from Animal ,
giving it functionality specific to each class. Note that even though tom is declared as an Animal , since its
value is a Horse , calling tom.move(34) will call the overriding method in Horse :

Slithering...
Sammy the Python moved 5m.
Galloping...
Tommy the Palomino moved 34m.

Public, private, and protected modifiers


Public by default
In our examples, we've been able to freely access the members that we declared throughout our programs.
If you're familiar with classes in other languages, you may have noticed in the above examples we haven't
had to use the word public to accomplish this; for instance, C# requires that each member be explicitly
labeled public to be visible. In TypeScript, each member is public by default.

You may still mark a member public explicitly. We could have written the Animal class from the previous
section in the following way:

class Animal {
public name: string;

public constructor(theName: string) {


this.name = theName;
}

public move(distanceInMeters: number) {


console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}

ECMAScript Private Fields


With TypeScript 3.8, TypeScript supports the new JavaScript syntax for private fields:

// @errors: 18013
class Animal {
#name: string;
constructor(theName: string) {
this.#name = theName;
}
}

new Animal("Cat").#name;

93
typescript

This syntax is built into the JavaScript runtime and can have better guarantees about the isolation of each
private field. Right now, the best documentation for these private fields is in the TypeScript 3.8 release
notes.

Understanding TypeScript's private


TypeScript also has its own way to declare a member as being marked private , it cannot be accessed
from outside of its containing class. For example:

// @errors: 2341
class Animal {
private name: string;

constructor(theName: string) {
this.name = theName;
}
}

new Animal("Cat").name;

TypeScript is a structural type system. When we compare two different types, regardless of where they
came from, if the types of all members are compatible, then we say the types themselves are compatible.

However, when comparing types that have private and protected members, we treat these types differ-
ently. For two types to be considered compatible, if one of them has a private member, then the other
must have a private member that originated in the same declaration. The same applies to protected
members.

Let's look at an example to better see how this plays out in practice:

// @errors: 2322
class Animal {
private name: string;
constructor(theName: string) {
this.name = theName;
}
}

class Rhino extends Animal {


constructor() {
super("Rhino");
}
}

class Employee {
private name: string;
constructor(theName: string) {
this.name = theName;
}
}

let animal = new Animal("Goat");


let rhino = new Rhino();
let employee = new Employee("Bob");

94
Classes

animal = rhino;
animal = employee;

In this example, we have an Animal and a Rhino , with Rhino being a subclass of Animal . We also have
a new class Employee that looks identical to Animal in terms of shape. We create some instances of these
classes and then try to assign them to each other to see what will happen. Because Animal and Rhino
share the private side of their shape from the same declaration of private name: string in Animal ,
they are compatible. However, this is not the case for Employee . When we try to assign from an Employee
to Animal we get an error that these types are not compatible. Even though Employee also has a pri‐
vate member called name , it's not the one we declared in Animal .

Understanding protected
The protected modifier acts much like the private modifier with the exception that members declared
protected can also be accessed within deriving classes. For example,

// @errors: 2445
class Person {
protected name: string;
constructor(name: string) {
this.name = name;
}
}

class Employee extends Person {


private department: string;

constructor(name: string, department: string) {


super(name);
this.department = department;
}

public getElevatorPitch() {
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
}

let howard = new Employee("Howard", "Sales");


console.log(howard.getElevatorPitch());
console.log(howard.name);

Notice that while we can't use name from outside of Person , we can still use it from within an instance
method of Employee because Employee derives from Person .

A constructor may also be marked protected . This means that the class cannot be instantiated outside of
its containing class, but can be extended. For example,

// @errors: 2674
class Person {
protected name: string;
protected constructor(theName: string) {
this.name = theName;
}
}

95
typescript

// Employee can extend Person


class Employee extends Person {
private department: string;

constructor(name: string, department: string) {


super(name);
this.department = department;
}

public getElevatorPitch() {
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
}

let howard = new Employee("Howard", "Sales");


let john = new Person("John");

Readonly modifier
You can make properties readonly by using the readonly keyword. Readonly properties must be initialized
at their declaration or in the constructor.

// @errors: 2540
class Octopus {
readonly name: string;
readonly numberOfLegs: number = 8;

constructor(theName: string) {
this.name = theName;
}
}

let dad = new Octopus("Man with the 8 strong legs");


dad.name = "Man with the 3-piece suit";

Parameter properties
In our last example, we had to declare a readonly member name and a constructor parameter theName in
the Octopus class. This is needed in order to have the value of theName accessible after the Octopus
constructor is executed. Parameter properties let you create and initialize a member in one place. Here's a
further revision of the previous Octopus class using a parameter property:

class Octopus {
readonly numberOfLegs: number = 8;
constructor(readonly name: string) {}
}

let dad = new Octopus("Man with the 8 strong legs");


dad.name;

Notice how we dropped theName altogether and just use the shortened readonly name: string parame-
ter on the constructor to create and initialize the name member. We've consolidated the declarations and
assignment into one location.

96
Classes

Parameter properties are declared by prefixing a constructor parameter with an accessibility modifier or
readonly , or both. Using private for a parameter property declares and initializes a private member;
likewise, the same is done for public , protected , and readonly .

Accessors
TypeScript supports getters/setters as a way of intercepting accesses to a member of an object. This gives
you a way of having finer-grained control over how a member is accessed on each object.

Let's convert a simple class to use get and set . First, let's start with an example without getters and
setters.

// @strict: false
class Employee {
fullName: string;
}

let employee = new Employee();


employee.fullName = "Bob Smith";

if (employee.fullName) {
console.log(employee.fullName);
}

While allowing people to randomly set fullName directly is pretty handy, we may also want enforce some
constraints when fullName is set.

In this version, we add a setter that checks the length of the newName to make sure it's compatible with
the max-length of our backing database field. If it isn't we throw an error notifying client code that some-
thing went wrong.

To preserve existing functionality, we also add a simple getter that retrieves fullName unmodified.

// @strict: false
const fullNameMaxLength = 10;

class Employee {
private _fullName: string = "";

get fullName(): string {


return this._fullName;
}

set fullName(newName: string) {


if (newName && newName.length > fullNameMaxLength) {
throw new Error("fullName has a max length of " + fullNameMaxLength);
}

this._fullName = newName;
}
}

let employee = new Employee();


employee.fullName = "Bob Smith";

97
typescript

if (employee.fullName) {
console.log(employee.fullName);
}

To prove to ourselves that our accessor is now checking the length of values, we can attempt to assign a
name longer than 10 characters and verify that we get an error.

A couple of things to note about accessors:

First, accessors require you to set the compiler to output ECMAScript 5 or higher. Downleveling to
ECMAScript 3 is not supported. Second, accessors with a get and no set are automatically inferred to be
readonly . This is helpful when generating a .d.ts file from your code, because users of your property
can see that they can't change it.

Static Properties
Up to this point, we've only talked about the instance members of the class, those that show up on the ob-
ject when it's instantiated. We can also create static members of a class, those that are visible on the class
itself rather than on the instances. In this example, we use static on the origin, as it's a general value for
all grids. Each instance accesses this value through prepending the name of the class. Similarly to prepend-
ing this. in front of instance accesses, here we prepend Grid. in front of static accesses.

class Grid {
static origin = { x: 0, y: 0 };

calculateDistanceFromOrigin(point: { x: number; y: number }) {


let xDist = point.x - Grid.origin.x;
let yDist = point.y - Grid.origin.y;
return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
}

constructor(public scale: number) {}


}

let grid1 = new Grid(1.0); // 1x scale


let grid2 = new Grid(5.0); // 5x scale

console.log(grid1.calculateDistanceFromOrigin({ x: 10, y: 10 }));


console.log(grid2.calculateDistanceFromOrigin({ x: 10, y: 10 }));

Abstract Classes
Abstract classes are base classes from which other classes may be derived. They may not be instantiated
directly. Unlike an interface, an abstract class may contain implementation details for its members. The ab‐
stract keyword is used to define abstract classes as well as abstract methods within an abstract class.

abstract class Animal {


abstract makeSound(): void;

move(): void {
console.log("roaming the earth...");
}
}

98
Classes

Methods within an abstract class that are marked as abstract do not contain an implementation and must be
implemented in derived classes. Abstract methods share a similar syntax to interface methods. Both define
the signature of a method without including a method body. However, abstract methods must include the
abstract keyword and may optionally include access modifiers.

// @errors: 2511 2339


abstract class Department {
constructor(public name: string) {}

printName(): void {
console.log("Department name: " + this.name);
}

abstract printMeeting(): void; // must be implemented in derived classes


}

class AccountingDepartment extends Department {


constructor() {
super("Accounting and Auditing"); // constructors in derived classes must call
super()
}

printMeeting(): void {
console.log("The Accounting Department meets each Monday at 10am.");
}

generateReports(): void {
console.log("Generating accounting reports...");
}
}

let department: Department; // ok to create a reference to an abstract type


department = new Department(); // error: cannot create an instance of an abstract
class
department = new AccountingDepartment(); // ok to create and assign a non-abstract
subclass
department.printName();
department.printMeeting();
department.generateReports(); // error: department is not of type
AccountingDepartment, cannot access generateReports

Advanced Techniques
Constructor functions
When you declare a class in TypeScript, you are actually creating multiple declarations at the same time.
The first is the type of the instance of the class.

class Greeter {
greeting: string;

constructor(message: string) {
this.greeting = message;
}

greet() {
return "Hello, " + this.greeting;

99
typescript

}
}

let greeter: Greeter;


greeter = new Greeter("world");
console.log(greeter.greet()); // "Hello, world"

Here, when we say let greeter: Greeter , we're using Greeter as the type of instances of the class
Greeter . This is almost second nature to programmers from other object-oriented languages.

We're also creating another value that we call the constructor function. This is the function that is called
when we new up instances of the class. To see what this looks like in practice, let's take a look at the
JavaScript created by the above example:

// @strict: false
let Greeter = (function () {
function Greeter(message) {
this.greeting = message;
}

Greeter.prototype.greet = function () {
return "Hello, " + this.greeting;
};

return Greeter;
})();

let greeter;
greeter = new Greeter("world");
console.log(greeter.greet()); // "Hello, world"

Here, let Greeter is going to be assigned the constructor function. When we call new and run this func-
tion, we get an instance of the class. The constructor function also contains all of the static members of the
class. Another way to think of each class is that there is an instance side and a static side.

Let's modify the example a bit to show this difference:

// @strict: false
class Greeter {
static standardGreeting = "Hello, there";
greeting: string;
greet() {
if (this.greeting) {
return "Hello, " + this.greeting;
} else {
return Greeter.standardGreeting;
}
}
}

let greeter1: Greeter;


greeter1 = new Greeter();
console.log(greeter1.greet()); // "Hello, there"

let greeterMaker: typeof Greeter = Greeter;


greeterMaker.standardGreeting = "Hey there!";

let greeter2: Greeter = new greeterMaker();

100
Classes

console.log(greeter2.greet()); // "Hey there!"

let greeter3: Greeter;


greeter3 = new Greeter();
console.log(greeter3.greet()); // "Hey there!"

In this example, greeter1 works similarly to before. We instantiate the Greeter class, and use this ob-
ject. This we have seen before.

Next, we then use the class directly. Here we create a new variable called greeterMaker . This variable will
hold the class itself, or said another way its constructor function. Here we use typeof Greeter , that is
"give me the type of the Greeter class itself" rather than the instance type. Or, more precisely, "give me
the type of the symbol called Greeter ," which is the type of the constructor function. This type will contain
all of the static members of Greeter along with the constructor that creates instances of the Greeter class.
We show this by using new on greeterMaker , creating new instances of Greeter and invoking them as
before. It is also good to mention that changing static property is frowned upon, here greeter3 has "Hey
there!" instead of "Hello, there" on standardGreeting .

Using a class as an interface


As we said in the previous section, a class declaration creates two things: a type representing instances of
the class and a constructor function. Because classes create types, you can use them in the same places
you would be able to use interfaces.

// @strict: false
class Point {
x: number;
y: number;
}

interface Point3d extends Point {


z: number;
}

let point3d: Point3d = { x: 1, y: 2, z: 3 };

Go to TOC

101
typescript

Functions are the fundamental building block of any application in JavaScript. They're how you build up lay-
ers of abstraction, mimicking classes, information hiding, and modules. In TypeScript, while there are class-
es, namespaces, and modules, functions still play the key role in describing how to do things. TypeScript
also adds some new capabilities to the standard JavaScript functions to make them easier to work with.

Functions
To begin, just as in JavaScript, TypeScript functions can be created both as a named function or as an
anonymous function. This allows you to choose the most appropriate approach for your application, whether
you're building a list of functions in an API or a one-off function to hand off to another function.

To quickly recap what these two approaches look like in JavaScript:

// @strict: false
// Named function
function add(x, y) {
return x + y;
}

// Anonymous function
let myAdd = function (x, y) {
return x + y;
};

Just as in JavaScript, functions can refer to variables outside of the function body. When they do so, they're
said to capture these variables. While understanding how this works (and the trade-offs when using this
technique) is outside of the scope of this article, having a firm understanding how this mechanic works is an
important piece of working with JavaScript and TypeScript.

// @strict: false
let z = 100;

function addToZ(x, y) {
return x + y + z;
}

Function Types
Typing the function
Let's add types to our simple examples from earlier:

function add(x: number, y: number): number {


return x + y;
}

let myAdd = function (x: number, y: number): number {


return x + y;
};

102
Functions

We can add types to each of the parameters and then to the function itself to add a return type. TypeScript
can figure the return type out by looking at the return statements, so we can also optionally leave this off in
many cases.

Writing the function type


Now that we've typed the function, let's write the full type of the function out by looking at each piece of
the function type.

let myAdd: (x: number, y: number) => number = function (


x: number,
y: number
): number {
return x + y;
};

A function's type has the same two parts: the type of the arguments and the return type. When writing out
the whole function type, both parts are required. We write out the parameter types just like a parameter
list, giving each parameter a name and a type. This name is just to help with readability. We could have in-
stead written:

let myAdd: (baseValue: number, increment: number) => number = function (


x: number,
y: number
): number {
return x + y;
};

As long as the parameter types line up, it's considered a valid type for the function, regardless of the names
you give the parameters in the function type.

The second part is the return type. We make it clear which is the return type by using an arrow ( => ) be-
tween the parameters and the return type. As mentioned before, this is a required part of the function type,
so if the function doesn't return a value, you would use void instead of leaving it off.

Of note, only the parameters and the return type make up the function type. Captured variables are not re-
flected in the type. In effect, captured variables are part of the "hidden state" of any function and do not
make up its API.

Inferring the types


In playing with the example, you may notice that the TypeScript compiler can figure out the type even if
you only have types on one side of the equation:

// The parameters 'x' and 'y' have the type number


let myAdd = function (x: number, y: number): number {
return x + y;
};

// myAdd has the full function type

103
typescript

let myAdd2: (baseValue: number, increment: number) => number = function (x, y) {
return x + y;
};

This is called "contextual typing", a form of type inference. This helps cut down on the amount of effort to
keep your program typed.

Optional and Default Parameters


In TypeScript, every parameter is assumed to be required by the function. This doesn't mean that it can't be
given null or undefined , but rather, when the function is called, the compiler will check that the user has
provided a value for each parameter. The compiler also assumes that these parameters are the only para-
meters that will be passed to the function. In short, the number of arguments given to a function has to
match the number of parameters the function expects.

// @errors: 2554
function buildName(firstName: string, lastName: string) {
return firstName + " " + lastName;
}

let result1 = buildName("Bob"); // error, too few parameters


let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
let result3 = buildName("Bob", "Adams"); // ah, just right

In JavaScript, every parameter is optional, and users may leave them off as they see fit. When they do,
their value is undefined . We can get this functionality in TypeScript by adding a ? to the end of parame-
ters we want to be optional. For example, let's say we want the last name parameter from above to be
optional:

// @errors: 2554
function buildName(firstName: string, lastName?: string) {
if (lastName) return firstName + " " + lastName;
else return firstName;
}

let result1 = buildName("Bob"); // works correctly now


let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
let result3 = buildName("Bob", "Adams"); // ah, just right

Any optional parameters must follow required parameters. Had we wanted to make the first name optional,
rather than the last name, we would need to change the order of parameters in the function, putting the
first name last in the list.

In TypeScript, we can also set a value that a parameter will be assigned if the user does not provide one, or
if the user passes undefined in its place. These are called default-initialized parameters. Let's take the
previous example and default the last name to "Smith" .

// @errors: 2554
function buildName(firstName: string, lastName = "Smith") {
return firstName + " " + lastName;
}

let result1 = buildName("Bob"); // works correctly now, returns "Bob Smith"

104
Functions

let result2 = buildName("Bob", undefined); // still works, also returns "Bob


Smith"
let result3 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
let result4 = buildName("Bob", "Adams"); // ah, just right

Default-initialized parameters that come after all required parameters are treated as optional, and just like
optional parameters, can be omitted when calling their respective function. This means optional parameters
and trailing default parameters will share commonality in their types, so both

function buildName(firstName: string, lastName?: string) {


// ...
}

and

function buildName(firstName: string, lastName = "Smith") {


// ...
}

share the same type (firstName: string, lastName?: string) => string . The default value of last‐
Name disappears in the type, only leaving behind the fact that the parameter is optional.

Unlike plain optional parameters, default-initialized parameters don't need to occur after required parame-
ters. If a default-initialized parameter comes before a required parameter, users need to explicitly pass un‐
defined to get the default initialized value. For example, we could write our last example with only a de-
fault initializer on firstName :

// @errors: 2554
function buildName(firstName = "Will", lastName: string) {
return firstName + " " + lastName;
}

let result1 = buildName("Bob"); // error, too few parameters


let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
let result3 = buildName("Bob", "Adams"); // okay and returns "Bob Adams"
let result4 = buildName(undefined, "Adams"); // okay and returns "Will Adams"

Rest Parameters
Required, optional, and default parameters all have one thing in common: they talk about one parameter at
a time. Sometimes, you want to work with multiple parameters as a group, or you may not know how many
parameters a function will ultimately take. In JavaScript, you can work with the arguments directly using
the arguments variable that is visible inside every function body.

In TypeScript, you can gather these arguments together into a variable:

function buildName(firstName: string, ...restOfName: string[]) {


return firstName + " " + restOfName.join(" ");
}

// employeeName will be "Joseph Samuel Lucas MacKinzie"


let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");

105
typescript

Rest parameters are treated as a boundless number of optional parameters. When passing arguments for a
rest parameter, you can use as many as you want; you can even pass none. The compiler will build an array
of the arguments passed in with the name given after the ellipsis ( ... ), allowing you to use it in your
function.

The ellipsis is also used in the type of the function with rest parameters:

function buildName(firstName: string, ...restOfName: string[]) {


return firstName + " " + restOfName.join(" ");
}

let buildNameFun: (fname: string, ...rest: string[]) => string = buildName;

this
Learning how to use this in JavaScript is something of a rite of passage. Since TypeScript is a superset of
JavaScript, TypeScript developers also need to learn how to use this and how to spot when it's not being
used correctly. Fortunately, TypeScript lets you catch incorrect uses of this with a couple of techniques. If
you need to learn how this works in JavaScript, though, first read Yehuda Katz's Understanding JavaScript
Function Invocation and "this". Yehuda's article explains the inner workings of this very well, so we'll just
cover the basics here.

this and arrow functions


In JavaScript, this is a variable that's set when a function is called. This makes it a very powerful and
flexible feature, but it comes at the cost of always having to know about the context that a function is exe-
cuting in. This is notoriously confusing, especially when returning a function or passing a function as an
argument.

Let's look at an example:

// @strict: false
let deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
createCardPicker: function () {
return function () {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);

return { suit: this.suits[pickedSuit], card: pickedCard % 13 };


};
},
};

let cardPicker = deck.createCardPicker();


let pickedCard = cardPicker();

alert("card: " + pickedCard.card + " of " + pickedCard.suit);

106
Functions

Notice that createCardPicker is a function that itself returns a function. If we tried to run the example,
we would get an error instead of the expected alert box. This is because the this being used in the func-
tion created by createCardPicker will be set to window instead of our deck object. That's because we
call cardPicker() on its own. A top-level non-method syntax call like this will use window for this .
(Note: under strict mode, this will be undefined rather than window ).

We can fix this by making sure the function is bound to the correct this before we return the function to
be used later. This way, regardless of how it's later used, it will still be able to see the original deck object.
To do this, we change the function expression to use the ECMAScript 6 arrow syntax. Arrow functions cap-
ture the this where the function is created rather than where it is invoked:

// @strict: false
let deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
createCardPicker: function () {
// NOTE: the line below is now an arrow function, allowing us to capture
'this' right here
return () => {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);

return { suit: this.suits[pickedSuit], card: pickedCard % 13 };


};
},
};

let cardPicker = deck.createCardPicker();


let pickedCard = cardPicker();

alert("card: " + pickedCard.card + " of " + pickedCard.suit);

Even better, TypeScript will warn you when you make this mistake if you pass the noImplicitThis flag to
the compiler. It will point out that this in this.suits[pickedSuit] is of type any .

this parameters
Unfortunately, the type of this.suits[pickedSuit] is still any . That's because this comes from the
function expression inside the object literal. To fix this, you can provide an explicit this parameter. this
parameters are fake parameters that come first in the parameter list of a function:

function f(this: void) {


// make sure `this` is unusable in this standalone function
}

Let's add a couple of interfaces to our example above, Card and Deck , to make the types clearer and eas-
ier to reuse:

interface Card {
suit: string;
card: number;
}

107
typescript

interface Deck {
suits: string[];
cards: number[];
createCardPicker(this: Deck): () => Card;
}

let deck: Deck = {


suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
// NOTE: The function now explicitly specifies that its callee must be of type
Deck
createCardPicker: function (this: Deck) {
return () => {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);

return { suit: this.suits[pickedSuit], card: pickedCard % 13 };


};
},
};

let cardPicker = deck.createCardPicker();


let pickedCard = cardPicker();

alert("card: " + pickedCard.card + " of " + pickedCard.suit);

Now TypeScript knows that createCardPicker expects to be called on a Deck object. That means that
this is of type Deck now, not any , so noImplicitThis will not cause any errors.

this parameters in callbacks


You can also run into errors with this in callbacks, when you pass functions to a library that will later call
them. Because the library that calls your callback will call it like a normal function, this will be unde‐
fined . With some work you can use this parameters to prevent errors with callbacks too. First, the li-
brary author needs to annotate the callback type with this :

interface UIElement {
addClickListener(onclick: (this: void, e: Event) => void): void;
}

this: void means that addClickListener expects onclick to be a function that does not require a
this type. Second, annotate your calling code with this :

// @strict: false
// @errors: 2345
interface UIElement {
addClickListener(onclick: (this: void, e: Event) => void): void;
}
interface Event {
message: string;
}
declare const uiElement: UIElement;
// ---cut---
class Handler {
info: string;
onClickBad(this: Handler, e: Event) {
// oops, used `this` here. using this callback would crash at runtime

108
Functions

this.info = e.message;
}
}

let h = new Handler();


uiElement.addClickListener(h.onClickBad); // error!

With this annotated, you make it explicit that onClickBad must be called on an instance of Handler .
Then TypeScript will detect that addClickListener requires a function that has this: void . To fix the
error, change the type of this :

// @strict: false
interface UIElement {
addClickListener(onclick: (this: void, e: Event) => void): void;
}
interface Event {
message: string;
}
declare const uiElement: UIElement;
// ---cut---
class Handler {
info: string;
onClickGood(this: void, e: Event) {
// can't use `this` here because it's of type void!
console.log("clicked!");
}
}

let h = new Handler();


uiElement.addClickListener(h.onClickGood);

Because onClickGood specifies its this type as void , it is legal to pass to addClickListener . Of
course, this also means that it can't use this.info . If you want both then you'll have to use an arrow
function:

// @strict: false
interface UIElement {
addClickListener(onclick: (this: void, e: Event) => void): void;
}
interface Event {
message: string;
}
declare const uiElement: UIElement;
// ---cut---
class Handler {
info: string;
onClickGood = (e: Event) => {
this.info = e.message;
};
}

This works because arrow functions use the outer this , so you can always pass them to something that
expects this: void . The downside is that one arrow function is created per object of type Handler.
Methods, on the other hand, are only created once and attached to Handler's prototype. They are shared
between all objects of type Handler.

109
typescript

Overloads
JavaScript is inherently a very dynamic language. It's not uncommon for a single JavaScript function to re-
turn different types of objects based on the shape of the arguments passed in.

// @strict: false
let suits = ["hearts", "spades", "clubs", "diamonds"];

function pickCard(x: any): any {


// Check to see if we're working with an object/array
// if so, they gave us the deck and we'll pick the card
if (typeof x == "object") {
let pickedCard = Math.floor(Math.random() * x.length);
return pickedCard;
}
// Otherwise just let them pick the card
else if (typeof x == "number") {
let pickedSuit = Math.floor(x / 13);
return { suit: suits[pickedSuit], card: x % 13 };
}
}

let myDeck = [
{ suit: "diamonds", card: 2 },
{ suit: "spades", card: 10 },
{ suit: "hearts", card: 4 },
];

let pickedCard1 = myDeck[pickCard(myDeck)];


alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);

let pickedCard2 = pickCard(15);


alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);

Here, the pickCard function will return two different things based on what the user has passed in. If the
users passes in an object that represents the deck, the function will pick the card. If the user picks the card,
we tell them which card they've picked. But how do we describe this to the type system?

The answer is to supply multiple function types for the same function as a list of overloads. This list is what
the compiler will use to resolve function calls. Let's create a list of overloads that describe what our pick‐
Card accepts and what it returns.

let suits = ["hearts", "spades", "clubs", "diamonds"];

function pickCard(x: { suit: string; card: number }[]): number;


function pickCard(x: number): { suit: string; card: number };
function pickCard(x: any): any {
// Check to see if we're working with an object/array
// if so, they gave us the deck and we'll pick the card
if (typeof x == "object") {
let pickedCard = Math.floor(Math.random() * x.length);
return pickedCard;
}
// Otherwise just let them pick the card
else if (typeof x == "number") {
let pickedSuit = Math.floor(x / 13);
return { suit: suits[pickedSuit], card: x % 13 };

110
Functions

}
}

let myDeck = [
{ suit: "diamonds", card: 2 },
{ suit: "spades", card: 10 },
{ suit: "hearts", card: 4 },
];

let pickedCard1 = myDeck[pickCard(myDeck)];


alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);

let pickedCard2 = pickCard(15);


alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);

With this change, the overloads now give us type checked calls to the pickCard function.

In order for the compiler to pick the correct type check, it follows a similar process to the underlying
JavaScript. It looks at the overload list and, proceeding with the first overload, attempts to call the function
with the provided parameters. If it finds a match, it picks this overload as the correct overload. For this rea-
son, it's customary to order overloads from most specific to least specific.

Note that the function pickCard(x): any piece is not part of the overload list, so it only has two over-
loads: one that takes an object and one that takes a number. Calling pickCard with any other parameter
types would cause an error.

Go to TOC

111
typescript

A major part of software engineering is building components that not only have well-defined and consistent
APIs, but are also reusable. Components that are capable of working on the data of today as well as the
data of tomorrow will give you the most flexible capabilities for building up large software systems.

In languages like C# and Java, one of the main tools in the toolbox for creating reusable components is
generics, that is, being able to create a component that can work over a variety of types rather than a sin-
gle one. This allows users to consume these components and use their own types.

Hello World of Generics


To start off, let's do the "hello world" of generics: the identity function. The identity function is a function
that will return back whatever is passed in. You can think of this in a similar way to the echo command.

Without generics, we would either have to give the identity function a specific type:

function identity(arg: number): number {


return arg;
}

Or, we could describe the identity function using the any type:

function identity(arg: any): any {


return arg;
}

While using any is certainly generic in that it will cause the function to accept any and all types for the type
of arg , we actually are losing the information about what that type was when the function returns. If we
passed in a number, the only information we have is that any type could be returned.

Instead, we need a way of capturing the type of the argument in such a way that we can also use it to de-
note what is being returned. Here, we will use a type variable, a special kind of variable that works on types
rather than values.

function identity<T>(arg: T): T {


return arg;
}

We've now added a type variable T to the identity function. This T allows us to capture the type the user
provides (e.g. number ), so that we can use that information later. Here, we use T again as the return
type. On inspection, we can now see the same type is used for the argument and the return type. This al-
lows us to traffic that type information in one side of the function and out the other.

We say that this version of the identity function is generic, as it works over a range of types. Unlike us-
ing any , it's also just as precise (ie, it doesn't lose any information) as the first identity function that
used numbers for the argument and return type.

Once we've written the generic identity function, we can call it in one of two ways. The first way is to pass
all of the arguments, including the type argument, to the function:

112
Generics

function identity<T>(arg: T): T {


return arg;
}
// ---cut---
let output = identity<string>("myString");
// ^?

Here we explicitly set T to be string as one of the arguments to the function call, denoted using the <>
around the arguments rather than () .

The second way is also perhaps the most common. Here we use type argument inference -- that is, we
want the compiler to set the value of T for us automatically based on the type of the argument we pass in:

function identity<T>(arg: T): T {


return arg;
}
// ---cut---
let output = identity("myString");
// ^?

Notice that we didn't have to explicitly pass the type in the angle brackets ( <> ); the compiler just looked at
the value "myString" , and set T to its type. While type argument inference can be a helpful tool to keep
code shorter and more readable, you may need to explicitly pass in the type arguments as we did in the
previous example when the compiler fails to infer the type, as may happen in more complex examples.

Working with Generic Type Variables


When you begin to use generics, you'll notice that when you create generic functions like identity , the
compiler will enforce that you use any generically typed parameters in the body of the function correctly.
That is, that you actually treat these parameters as if they could be any and all types.

Let's take our identity function from earlier:

function identity<T>(arg: T): T {


return arg;
}

What if we want to also log the length of the argument arg to the console with each call? We might be
tempted to write this:

// @errors: 2339
function loggingIdentity<T>(arg: T): T {
console.log(arg.length);
return arg;
}

When we do, the compiler will give us an error that we're using the .length member of arg , but nowhere
have we said that arg has this member. Remember, we said earlier that these type variables stand in for
any and all types, so someone using this function could have passed in a number instead, which does not
have a .length member.

113
typescript

Let's say that we've actually intended this function to work on arrays of T rather than T directly. Since
we're working with arrays, the .length member should be available. We can describe this just like we
would create arrays of other types:

function loggingIdentity<T>(arg: T[]): T[] {


console.log(arg.length);
return arg;
}

You can read the type of loggingIdentity as "the generic function loggingIdentity takes a type para-
meter T , and an argument arg which is an array of T s, and returns an array of T s." If we passed in an
array of numbers, we'd get an array of numbers back out, as T would bind to number . This allows us to
use our generic type variable T as part of the types we're working with, rather than the whole type, giving
us greater flexibility.

We can alternatively write the sample example this way:

function loggingIdentity<T>(arg: Array<T>): Array<T> {


console.log(arg.length); // Array has a .length, so no more error
return arg;
}

You may already be familiar with this style of type from other languages. In the next section, we'll cover
how you can create your own generic types like Array<T> .

Generic Types
In previous sections, we created generic identity functions that worked over a range of types. In this
section, we'll explore the type of the functions themselves and how to create generic interfaces.

The type of generic functions is just like those of non-generic functions, with the type parameters listed
first, similarly to function declarations:

function identity<T>(arg: T): T {


return arg;
}

let myIdentity: <T>(arg: T) => T = identity;

We could also have used a different name for the generic type parameter in the type, so long as the number
of type variables and how the type variables are used line up.

function identity<T>(arg: T): T {


return arg;
}

let myIdentity: <U>(arg: U) => U = identity;

We can also write the generic type as a call signature of an object literal type:

114
Generics

function identity<T>(arg: T): T {


return arg;
}

let myIdentity: { <T>(arg: T): T } = identity;

Which leads us to writing our first generic interface. Let's take the object literal from the previous example
and move it to an interface:

interface GenericIdentityFn {
<T>(arg: T): T;
}

function identity<T>(arg: T): T {


return arg;
}

let myIdentity: GenericIdentityFn = identity;

In a similar example, we may want to move the generic parameter to be a parameter of the whole
interface. This lets us see what type(s) we're generic over (e.g. Dictionary<string> rather than just
Dictionary ). This makes the type parameter visible to all the other members of the interface.

interface GenericIdentityFn<T> {
(arg: T): T;
}

function identity<T>(arg: T): T {


return arg;
}

let myIdentity: GenericIdentityFn<number> = identity;

Notice that our example has changed to be something slightly different. Instead of describing a generic
function, we now have a non-generic function signature that is a part of a generic type. When we use
GenericIdentityFn , we now will also need to specify the corresponding type argument (here: number ),
effectively locking in what the underlying call signature will use. Understanding when to put the type para-
meter directly on the call signature and when to put it on the interface itself will be helpful in describing
what aspects of a type are generic.

In addition to generic interfaces, we can also create generic classes. Note that it is not possible to create
generic enums and namespaces.

Generic Classes
A generic class has a similar shape to a generic interface. Generic classes have a generic type parameter list
in angle brackets ( <> ) following the name of the class.

// @strict: false
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}

115
typescript

let myGenericNumber = new GenericNumber<number>();


myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) {
return x + y;
};

This is a pretty literal use of the GenericNumber class, but you may have noticed that nothing is restricting
it to only use the number type. We could have instead used string or even more complex objects.

// @strict: false
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
// ---cut---
let stringNumeric = new GenericNumber<string>();
stringNumeric.zeroValue = "";
stringNumeric.add = function (x, y) {
return x + y;
};

console.log(stringNumeric.add(stringNumeric.zeroValue, "test"));

Just as with interface, putting the type parameter on the class itself lets us make sure all of the properties
of the class are working with the same type.

As we covered in our section on classes, a class has two sides to its type: the static side and the instance
side. Generic classes are only generic over their instance side rather than their static side, so when working
with classes, static members can not use the class's type parameter.

Generic Constraints
If you remember from an earlier example, you may sometimes want to write a generic function that works
on a set of types where you have some knowledge about what capabilities that set of types will have. In our
loggingIdentity example, we wanted to be able to access the .length property of arg , but the com-
piler could not prove that every type had a .length property, so it warns us that we can't make this
assumption.

// @errors: 2339
function loggingIdentity<T>(arg: T): T {
console.log(arg.length);
return arg;
}

Instead of working with any and all types, we'd like to constrain this function to work with any and all types
that also have the .length property. As long as the type has this member, we'll allow it, but it's required
to have at least this member. To do so, we must list our requirement as a constraint on what T can be.

To do so, we'll create an interface that describes our constraint. Here, we'll create an interface that has a
single .length property and then we'll use this interface and the extends keyword to denote our
constraint:

116
Generics

interface Lengthwise {
length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {


console.log(arg.length); // Now we know it has a .length property, so no more
error
return arg;
}

Because the generic function is now constrained, it will no longer work over any and all types:

// @errors: 2345
interface Lengthwise {
length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {


console.log(arg.length);
return arg;
}
// ---cut---
loggingIdentity(3);

Instead, we need to pass in values whose type has all the required properties:

interface Lengthwise {
length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {


console.log(arg.length);
return arg;
}
// ---cut---
loggingIdentity({ length: 10, value: 3 });

Using Type Parameters in Generic Constraints


You can declare a type parameter that is constrained by another type parameter. For example, here we'd
like to get a property from an object given its name. We'd like to ensure that we're not accidentally grab-
bing a property that does not exist on the obj , so we'll place a constraint between the two types:

// @errors: 2345
function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}

let x = { a: 1, b: 2, c: 3, d: 4 };

getProperty(x, "a");
getProperty(x, "m");

117
typescript

Using Class Types in Generics


When creating factories in TypeScript using generics, it is necessary to refer to class types by their con-
structor functions. For example,

function create<T>(c: { new (): T }): T {


return new c();
}

A more advanced example uses the prototype property to infer and constrain relationships between the
constructor function and the instance side of class types.

// @strict: false
class BeeKeeper {
hasMask: boolean;
}

class ZooKeeper {
nametag: string;
}

class Animal {
numLegs: number;
}

class Bee extends Animal {


keeper: BeeKeeper;
}

class Lion extends Animal {


keeper: ZooKeeper;
}

function createInstance<A extends Animal>(c: new () => A): A {


return new c();
}

createInstance(Lion).keeper.nametag;
createInstance(Bee).keeper.hasMask;

Go to TOC

118
Interfaces

One of TypeScript's core principles is that type checking focuses on the shape that values have. This is
sometimes called "duck typing" or "structural subtyping". In TypeScript, interfaces fill the role of naming
these types, and are a powerful way of defining contracts within your code as well as contracts with code
outside of your project.

Our First Interface


The easiest way to see how interfaces work is to start with a simple example:

function printLabel(labeledObj: { label: string }) {


console.log(labeledObj.label);
}

let myObj = { size: 10, label: "Size 10 Object" };


printLabel(myObj);

The type checker checks the call to printLabel . The printLabel function has a single parameter that re-
quires that the object passed in has a property called label of type string . Notice that our object actual-
ly has more properties than this, but the compiler only checks that at least the ones required are present
and match the types required. There are some cases where TypeScript isn't as lenient, which we'll cover in a
bit.

We can write the same example again, this time using an interface to describe the requirement of having
the label property that is a string:

interface LabeledValue {
label: string;
}

function printLabel(labeledObj: LabeledValue) {


console.log(labeledObj.label);
}

let myObj = { size: 10, label: "Size 10 Object" };


printLabel(myObj);

The interface LabeledValue is a name we can now use to describe the requirement in the previous exam-
ple. It still represents having a single property called label that is of type string . Notice we didn't have
to explicitly say that the object we pass to printLabel implements this interface like we might have to in
other languages. Here, it's only the shape that matters. If the object we pass to the function meets the re-
quirements listed, then it's allowed.

It's worth pointing out that the type checker does not require that these properties come in any sort of or-
der, only that the properties the interface requires are present and have the required type.

119
typescript

Optional Properties
Not all properties of an interface may be required. Some exist under certain conditions or may not be there
at all. These optional properties are popular when creating patterns like "option bags" where you pass an
object to a function that only has a couple of properties filled in.

Here's an example of this pattern:

interface SquareConfig {
color?: string;
width?: number;
}

function createSquare(config: SquareConfig): { color: string; area: number } {


let newSquare = { color: "white", area: 100 };
if (config.color) {
newSquare.color = config.color;
}
if (config.width) {
newSquare.area = config.width * config.width;
}
return newSquare;
}

let mySquare = createSquare({ color: "black" });

Interfaces with optional properties are written similar to other interfaces, with each optional property denot-
ed by a ? at the end of the property name in the declaration.

The advantage of optional properties is that you can describe these possibly available properties while still
also preventing use of properties that are not part of the interface. For example, had we mistyped the name
of the color property in createSquare , we would get an error message letting us know:

// @errors: 2551
interface SquareConfig {
color?: string;
width?: number;
}

function createSquare(config: SquareConfig): { color: string; area: number } {


let newSquare = { color: "white", area: 100 };
if (config.clor) {
// Error: Property 'clor' does not exist on type 'SquareConfig'
newSquare.color = config.clor;
}
if (config.width) {
newSquare.area = config.width * config.width;
}
return newSquare;
}

let mySquare = createSquare({ color: "black" });

120
Interfaces

Readonly properties
Some properties should only be modifiable when an object is first created. You can specify this by putting
readonly before the name of the property:

interface Point {
readonly x: number;
readonly y: number;
}

You can construct a Point by assigning an object literal. After the assignment, x and y can't be
changed.

// @errors: 2540
interface Point {
readonly x: number;
readonly y: number;
}
// ---cut---
let p1: Point = { x: 10, y: 20 };
p1.x = 5; // error!

TypeScript comes with a ReadonlyArray<T> type that is the same as Array<T> with all mutating methods
removed, so you can make sure you don't change your arrays after creation:

// @errors: 2542 2339 2540 4104


let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;

ro[0] = 12; // error!


ro.push(5); // error!
ro.length = 100; // error!
a = ro; // error!

On the last line of the snippet you can see that even assigning the entire ReadonlyArray back to a normal
array is illegal. You can still override it with a type assertion, though:

let a: number[] = [1, 2, 3, 4];


let ro: ReadonlyArray<number> = a;

a = ro as number[];

readonly vs const
The easiest way to remember whether to use readonly or const is to ask whether you're using it on a
variable or a property. Variables use const whereas properties use readonly .

Excess Property Checks


In our first example using interfaces, TypeScript lets us pass { size: number; label: string; } to
something that only expected a { label: string; } . We also just learned about optional properties, and
how they're useful when describing so-called "option bags".

121
typescript

However, combining the two naively would allow an error to sneak in. For example, taking our last example
using createSquare :

// @errors: 2345 2739


interface SquareConfig {
color?: string;
width?: number;
}

function createSquare(config: SquareConfig): { color: string; area: number } {


return {
color: config.color || "red",
area: config.width ? config.width * config.width : 20,
};
}

let mySquare = createSquare({ colour: "red", width: 100 });

Notice the given argument to createSquare is spelled colour instead of color . In plain JavaScript, this
sort of thing fails silently.

You could argue that this program is correctly typed, since the width properties are compatible, there's no
color property present, and the extra colour property is insignificant.

However, TypeScript takes the stance that there's probably a bug in this code. Object literals get special
treatment and undergo excess property checking when assigning them to other variables, or passing them
as arguments. If an object literal has any properties that the "target type" doesn't have, you'll get an error:

// @errors: 2345 2739


interface SquareConfig {
color?: string;
width?: number;
}

function createSquare(config: SquareConfig): { color: string; area: number } {


return {
color: config.color || "red",
area: config.width ? config.width * config.width : 20,
};
}
// ---cut---
let mySquare = createSquare({ colour: "red", width: 100 });

Getting around these checks is actually really simple. The easiest method is to just use a type assertion:

// @errors: 2345 2739


interface SquareConfig {
color?: string;
width?: number;
}

function createSquare(config: SquareConfig): { color: string; area: number } {


return {
color: config.color || "red",
area: config.width ? config.width * config.width : 20,
};

122
Interfaces

}
// ---cut---
let mySquare = createSquare({ width: 100, opacity: 0.5 } as SquareConfig);

However, a better approach might be to add a string index signature if you're sure that the object can have
some extra properties that are used in some special way. If SquareConfig can have color and width
properties with the above types, but could also have any number of other properties, then we could define it
like so:

interface SquareConfig {
color?: string;
width?: number;
[propName: string]: any;
}

We'll discuss index signatures in a bit, but here we're saying a SquareConfig can have any number of
properties, and as long as they aren't color or width , their types don't matter.

One final way to get around these checks, which might be a bit surprising, is to assign the object to another
variable: Since squareOptions won't undergo excess property checks, the compiler won't give you an
error.

interface SquareConfig {
color?: string;
width?: number;
[propName: string]: any;
}

function createSquare(config: SquareConfig): { color: string; area: number } {


return {
color: config.color || "red",
area: config.width ? config.width * config.width : 20,
};
}
// ---cut---
let squareOptions = { colour: "red", width: 100 };
let mySquare = createSquare(squareOptions);

The above workaround will work as long as you have a common property between squareOptions and
SquareConfig . In this example, it was the property width . It will however, fail if the variable does not
have any common object property. For example:

// @errors: 2559
interface SquareConfig {
color?: string;
width?: number;
}

function createSquare(config: SquareConfig): { color: string; area: number } {


return {
color: config.color || "red",
area: config.width ? config.width * config.width : 20,
};
}

123
typescript

// ---cut---
let squareOptions = { colour: "red" };
let mySquare = createSquare(squareOptions);

Keep in mind that for simple code like above, you probably shouldn't be trying to "get around" these checks.
For more complex object literals that have methods and hold state, you might need to keep these tech-
niques in mind, but a majority of excess property errors are actually bugs. That means if you're running into
excess property checking problems for something like option bags, you might need to revise some of your
type declarations. In this instance, if it's okay to pass an object with both a color or colour property to
createSquare , you should fix up the definition of SquareConfig to reflect that.

Function Types
Interfaces are capable of describing the wide range of shapes that JavaScript objects can take. In addition
to describing an object with properties, interfaces are also capable of describing function types.

To describe a function type with an interface, we give the interface a call signature. This is like a function
declaration with only the parameter list and return type given. Each parameter in the parameter list re-
quires both name and type.

interface SearchFunc {
(source: string, subString: string): boolean;
}

Once defined, we can use this function type interface like we would other interfaces. Here, we show how
you can create a variable of a function type and assign it a function value of the same type.

interface SearchFunc {
(source: string, subString: string): boolean;
}
// ---cut---
let mySearch: SearchFunc;

mySearch = function (source: string, subString: string): boolean {


let result = source.search(subString);
return result > -1;
};

For function types to correctly type check, the names of the parameters do not need to match. We could
have, for example, written the above example like this:

interface SearchFunc {
(source: string, subString: string): boolean;
}
// ---cut---
let mySearch: SearchFunc;

mySearch = function (src: string, sub: string): boolean {


let result = src.search(sub);
return result > -1;
};

124
Interfaces

Function parameters are checked one at a time, with the type in each corresponding parameter position
checked against each other. If you do not want to specify types at all, TypeScript's contextual typing can in-
fer the argument types since the function value is assigned directly to a variable of type SearchFunc . Here,
also, the return type of our function expression is implied by the values it returns (here false and true ).

interface SearchFunc {
(source: string, subString: string): boolean;
}
// ---cut---
let mySearch: SearchFunc;

mySearch = function (src, sub) {


let result = src.search(sub);
return result > -1;
};

Had the function expression returned numbers or strings, the type checker would have made an error that
indicates return type doesn't match the return type described in the SearchFunc interface.

// @errors: 2322
interface SearchFunc {
(source: string, subString: string): boolean;
}
// ---cut---
let mySearch: SearchFunc;

mySearch = function (src, sub) {


let result = src.search(sub);
return "string";
};

Indexable Types
Similarly to how we can use interfaces to describe function types, we can also describe types that we can
"index into" like a[10] , or ageMap["daniel"] . Indexable types have an index signature that describes
the types we can use to index into the object, along with the corresponding return types when indexing.

Let's take an example:

interface StringArray {
[index: number]: string;
}

let myArray: StringArray;


myArray = ["Bob", "Fred"];

let myStr: string = myArray[0];

Above, we have a StringArray interface that has an index signature. This index signature states that
when a StringArray is indexed with a number , it will return a string .

There are four types of supported index signatures: string, number, symbol and template strings. It is pos-
sible to support many types of indexers, but the type returned from a numeric indexer must be a subtype of
the type returned from the string indexer.

125
typescript

This is because when indexing with a number , JavaScript will actually convert that to a string before in-
dexing into an object. That means that indexing with 100 (a number ) is the same thing as indexing with
"100" (a string ), so the two need to be consistent.

// @errors: 2413
// @strictPropertyInitialization: false
interface Animal {
name: string;
}

interface Dog extends Animal {


breed: string;
}

// Error: indexing with a numeric string might get you a completely separate type
of Animal!
interface NotOkay {
[x: number]: Animal;
[x: string]: Dog;
}

While string index signatures are a powerful way to describe the "dictionary" pattern, they also enforce that
all properties match their return type. This is because a string index declares that obj.property is also
available as obj["property"] . In the following example, name 's type does not match the string index's
type, and the type checker gives an error:

// @errors: 2411
interface NumberDictionary {
[index: string]: number;

length: number; // ok, length is a number


name: string; // error, the type of 'name' is not a subtype of the indexer
}

However, properties of different types are acceptable if the index signature is a union of the property types:

interface NumberOrStringDictionary {
[index: string]: number | string;

length: number; // ok, length is a number


name: string; // ok, name is a string
}

Finally, you can make index signatures readonly in order to prevent assignment to their indices:

// @errors: 2542
interface ReadonlyStringArray {
readonly [index: number]: string;
}

let myArray: ReadonlyStringArray = ["Alice", "Bob"];


myArray[2] = "Mallory"; // error!

You can't set myArray[2] because the index signature is readonly .

126
Interfaces

Indexable Types with Template Strings


A template string can be used to indicate that a particular pattern is allowed, but not all. For example, a
HTTP headers object may have a set list of known headers and support any custom defined properties which
are prefixed with x- .

// @errors: 2339

interface HeadersResponse {
"content-type": string,
date: string,
"content-length": string

// Permit any property starting with 'data-'.


[headerName: `x-${string}`]: string;
}

function handleResponse(r: HeadersResponse) {


// Handle known, and x- prefixed
const type = r["content-type"]
const poweredBy = r["x-powered-by"]

// Unknown keys without the prefix raise errors


const origin = r.origin
}

Class Types
Implementing an interface
One of the most common uses of interfaces in languages like C# and Java, that of explicitly enforcing that a
class meets a particular contract, is also possible in TypeScript.

interface ClockInterface {
currentTime: Date;
}

class Clock implements ClockInterface {


currentTime: Date = new Date();
constructor(h: number, m: number) {}
}

You can also describe methods in an interface that are implemented in the class, as we do with setTime in
the below example:

// @strictPropertyInitialization: false
interface ClockInterface {
currentTime: Date;
setTime(d: Date): void;
}

class Clock implements ClockInterface {


currentTime: Date = new Date();
setTime(d: Date) {
this.currentTime = d;

127
typescript

}
constructor(h: number, m: number) {}
}

Interfaces describe the public side of the class, rather than both the public and private side. This prohibits
you from using them to check that a class also has particular types for the private side of the class instance.

Difference between the static and instance sides of classes


When working with classes and interfaces, it helps to keep in mind that a class has two types: the type of
the static side and the type of the instance side. You may notice that if you create an interface with a con-
struct signature and try to create a class that implements this interface you get an error:

// @errors: 7013 2420 2564


// @strictPropertyInitialization: false
// @noImplicitAny: false
interface ClockConstructor {
new (hour: number, minute: number);
}

class Clock implements ClockConstructor {


currentTime: Date;
constructor(h: number, m: number) {}
}

This is because when a class implements an interface, only the instance side of the class is checked. Since
the constructor sits in the static side, it is not included in this check.

Instead, you would need to work with the static side of the class directly. In this example, we define two in-
terfaces, ClockConstructor for the constructor and ClockInterface for the instance methods. Then, for
convenience, we define a constructor function createClock that creates instances of the type that is
passed to it:

interface ClockConstructor {
new (hour: number, minute: number): ClockInterface;
}

interface ClockInterface {
tick(): void;
}

function createClock(
ctor: ClockConstructor,
hour: number,
minute: number
): ClockInterface {
return new ctor(hour, minute);
}

class DigitalClock implements ClockInterface {


constructor(h: number, m: number) {}
tick() {
console.log("beep beep");
}
}

128
Interfaces

class AnalogClock implements ClockInterface {


constructor(h: number, m: number) {}
tick() {
console.log("tick tock");
}
}

let digital = createClock(DigitalClock, 12, 17);


let analog = createClock(AnalogClock, 7, 32);

Because createClock 's first parameter is of type ClockConstructor , in createClock(AnalogClock, 7,


32) , it checks that AnalogClock has the correct constructor signature.

Another simple way is to use class expressions:

// @strictPropertyInitialization: false
// @noImplicitAny: false
interface ClockConstructor {
new (hour: number, minute: number): ClockInterface;
}

interface ClockInterface {
tick(): void;
}

const Clock: ClockConstructor = class Clock implements ClockInterface {


constructor(h: number, m: number) {}
tick() {
console.log("beep beep");
}
};

let clock = new Clock(12, 17);


clock.tick();

Extending Interfaces
Like classes, interfaces can extend each other. This allows you to copy the members of one interface into
another, which gives you more flexibility in how you separate your interfaces into reusable components.

interface Shape {
color: string;
}

interface Square extends Shape {


sideLength: number;
}

let square = {} as Square;


square.color = "blue";
square.sideLength = 10;

An interface can extend multiple interfaces, creating a combination of all of the interfaces.

interface Shape {
color: string;
}

129
typescript

interface PenStroke {
penWidth: number;
}

interface Square extends Shape, PenStroke {


sideLength: number;
}

let square = {} as Square;


square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;

Hybrid Types
As we mentioned earlier, interfaces can describe the rich types present in real world JavaScript. Because of
JavaScript's dynamic and flexible nature, you may occasionally encounter an object that works as a combi-
nation of some of the types described above.

One such example is an object that acts as both a function and an object, with additional properties:

interface Counter {
(start: number): string;
interval: number;
reset(): void;
}

function getCounter(): Counter {


let counter = function (start: number) {} as Counter;
counter.interval = 123;
counter.reset = function () {};
return counter;
}

let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;

When interacting with 3rd-party JavaScript, you may need to use patterns like the above to fully describe
the shape of the type.

Interfaces Extending Classes


When an interface type extends a class type it inherits the members of the class but not their implementa-
tions. It is as if the interface had declared all of the members of the class without providing an implementa-
tion. Interfaces inherit even the private and protected members of a base class. This means that when you
create an interface that extends a class with private or protected members, that interface type can only be
implemented by that class or a subclass of it.

This is useful when you have a large inheritance hierarchy, but want to specify that your code works with
only subclasses that have certain properties. The subclasses don't have to be related besides inheriting from
the base class. For example:

130
Interfaces

// @errors: 2300 2420 2300


class Control {
private state: any;
}

interface SelectableControl extends Control {


select(): void;
}

class Button extends Control implements SelectableControl {


select() {}
}

class TextBox extends Control {


select() {}
}

class ImageControl implements SelectableControl {


private state: any;
select() {}
}

In the above example, SelectableControl contains all of the members of Control , including the private
state property. Since state is a private member it is only possible for descendants of Control to imple-
ment SelectableControl . This is because only descendants of Control will have a state private mem-
ber that originates in the same declaration, which is a requirement for private members to be compatible.

Within the Control class it is possible to access the state private member through an instance of
SelectableControl . Effectively, a SelectableControl acts like a Control that is known to have a se‐
lect method. The Button and TextBox classes are subtypes of SelectableControl (because they both
inherit from Control and have a select method). The ImageControl class has its own state private
member rather than extending Control , so it cannot implement SelectableControl .

Go to TOC

131
typescript

A literal is a more concrete sub-type of a collective type. What this means is that "Hello World" is a
string , but a string is not "Hello World" inside the type system.

There are three sets of literal types available in TypeScript today: strings, numbers, and booleans; by using
literal types you can allow an exact value which a string, number, or boolean must have.

Literal Narrowing
When you declare a variable via var or let , you are telling the compiler that there is the chance that this
variable will change its contents. In contrast, using const to declare a variable will inform TypeScript that
this object will never change.

// We're making a guarantee that this variable


// helloWorld will never change, by using const.

// So, TypeScript sets the type to be "Hello World", not string


const helloWorld = "Hello World";

// On the other hand, a let can change, and so the compiler declares it a string
let hiWorld = "Hi World";

The process of going from an infinite number of potential cases (there is an infinite number of possible
string values) to a smaller, finite number of potential case (in helloWorld 's case: 1) is called narrowing.

String Literal Types


In practice string literal types combine nicely with union types, type guards, and type aliases. You can use
these features together to get enum-like behavior with strings.

// @errors: 2345
type Easing = "ease-in" | "ease-out" | "ease-in-out";

class UIElement {
animate(dx: number, dy: number, easing: Easing) {
if (easing === "ease-in") {
// ...
} else if (easing === "ease-out") {
} else if (easing === "ease-in-out") {
} else {
// It's possible that someone could reach this
// by ignoring your types though.
}
}
}

let button = new UIElement();


button.animate(0, 0, "ease-in");
button.animate(0, 0, "uneasy");

You can pass any of the three allowed strings, but any other string will give the error

Argument of type '"uneasy"' is not assignable to parameter of type '"ease-in" |


"ease-out" | "ease-in-out"'

132
Literal Types

String literal types can be used in the same way to distinguish overloads:

function createElement(tagName: "img"): HTMLImageElement;


function createElement(tagName: "input"): HTMLInputElement;
// ... more overloads ...
function createElement(tagName: string): Element {
// ... code goes here ...
}

Numeric Literal Types


TypeScript also has numeric literal types, which act the same as the string literals above.

function rollDice(): 1 | 2 | 3 | 4 | 5 | 6 {
return (Math.floor(Math.random() * 6) + 1) as 1 | 2 | 3 | 4 | 5 | 6;
}

const result = rollDice();

A common case for their use is for describing config values:

/** Creates a map centered at loc/lat */


declare function setupMap(config: MapConfig): void;
// ---cut---
interface MapConfig {
lng: number;
lat: number;
tileSize: 8 | 16 | 32;
}

setupMap({ lng: -73.935242, lat: 40.73061, tileSize: 16 });

Boolean Literal Types


TypeScript also has boolean literal types. You might use these to constrain object values whose properties
are interrelated.

interface ValidationSuccess {
isValid: true;
reason: null;
}

interface ValidationFailure {
isValid: false;
reason: string;
}

type ValidationResult = ValidationSuccess | ValidationFailure;

Go to TOC

133
typescript

So far, the handbook has covered types which are atomic objects. However, as you model more types you
find yourself looking for tools which let you compose or combine existing types instead of creating them
from scratch.

Intersection and Union types are one of the ways in which you can compose types.

Union Types
Occasionally, you'll run into a library that expects a parameter to be either a number or a string . For in-
stance, take the following function:

/**
* Takes a string and adds "padding" to the left.
* If 'padding' is a string, then 'padding' is appended to the left side.
* If 'padding' is a number, then that number of spaces is added to the left side.
*/
function padLeft(value: string, padding: any) {
if (typeof padding === "number") {
return Array(padding + 1).join(" ") + value;
}
if (typeof padding === "string") {
return padding + value;
}
throw new Error(`Expected string or number, got '${typeof padding}'.`);
}

padLeft("Hello world", 4); // returns " Hello world"

The problem with padLeft in the above example is that its padding parameter is typed as any . That
means that we can call it with an argument that's neither a number nor a string , but TypeScript will be
okay with it.

declare function padLeft(value: string, padding: any): string;


// ---cut---
// passes at compile time, fails at runtime.
let indentedString = padLeft("Hello world", true);

In traditional object-oriented code, we might abstract over the two types by creating a hierarchy of types.
While this is much more explicit, it's also a little bit overkill. One of the nice things about the original version
of padLeft was that we were able to just pass in primitives. That meant that usage was simple and con-
cise. This new approach also wouldn't help if we were just trying to use a function that already exists
elsewhere.

Instead of any , we can use a union type for the padding parameter:

// @errors: 2345
/**
* Takes a string and adds "padding" to the left.
* If 'padding' is a string, then 'padding' is appended to the left side.
* If 'padding' is a number, then that number of spaces is added to the left side.
*/
function padLeft(value: string, padding: string | number) {
// ...

134
Unions and Intersection Types

let indentedString = padLeft("Hello world", true);

A union type describes a value that can be one of several types. We use the vertical bar ( | ) to separate
each type, so number | string | boolean is the type of a value that can be a number , a string , or a
boolean .

Unions with Common Fields


If we have a value that is a union type, we can only access members that are common to all types in the
union.

// @errors: 2339

interface Bird {
fly(): void;
layEggs(): void;
}

interface Fish {
swim(): void;
layEggs(): void;
}

declare function getSmallPet(): Fish | Bird;

let pet = getSmallPet();


pet.layEggs();

// Only available in one of the two possible types


pet.swim();

Union types can be a bit tricky here, but it just takes a bit of intuition to get used to. If a value has the type
A | B , we only know for certain that it has members that both A and B have. In this example, Bird has
a member named fly . We can't be sure whether a variable typed as Bird | Fish has a fly method. If
the variable is really a Fish at runtime, then calling pet.fly() will fail.

Discriminating Unions
A common technique for working with unions is to have a single field which uses literal types which you can
use to let TypeScript narrow down the possible current type. For example, we're going to create a union of
three types which have a single shared field.

type NetworkLoadingState = {
state: "loading";
};

type NetworkFailedState = {
state: "failed";
code: number;
};

type NetworkSuccessState = {

135
typescript

state: "success";
response: {
title: string;
duration: number;
summary: string;
};
};

// Create a type which represents only one of the above types


// but you aren't sure which it is yet.
type NetworkState =
| NetworkLoadingState
| NetworkFailedState
| NetworkSuccessState;

All of the above types have a field named state , and then they also have their own fields:

NetworkLoadingState NetworkFailedState NetworkSuccessState

state state state

code response

Given the state field is common in every type inside NetworkState - it is safe for your code to access
without an existence check.

With state as a literal type, you can compare the value of state to the equivalent string and TypeScript
will know which type is currently being used.

NetworkLoadingState NetworkFailedState NetworkSuccessState


"loading" "failed" "success"

In this case, you can use a switch statement to narrow down which type is represented at runtime:

// @errors: 2339
type NetworkLoadingState = {
state: "loading";
};

type NetworkFailedState = {
state: "failed";
code: number;
};

type NetworkSuccessState = {
state: "success";
response: {
title: string;
duration: number;
summary: string;
};
};
// ---cut---
type NetworkState =
| NetworkLoadingState

136
Unions and Intersection Types

| NetworkFailedState
| NetworkSuccessState;

function logger(state: NetworkState): string {


// Right now TypeScript does not know which of the three
// potential types state could be.

// Trying to access a property which isn't shared


// across all types will raise an error
state.code;

// By switching on state, TypeScript can narrow the union


// down in code flow analysis
switch (state.state) {
case "loading":
return "Downloading...";
case "failed":
// The type must be NetworkFailedState here,
// so accessing the `code` field is safe
return `Error ${state.code} downloading`;
case "success":
return `Downloaded ${state.response.title} - ${state.response.summary}`;
}
}

Union Exhaustiveness checking


We would like the compiler to tell us when we don't cover all variants of the discriminated union. For exam-
ple, if we add NetworkFromCachedState to NetworkState , we need to update logger as well:

// @errors: 2366
type NetworkLoadingState = { state: "loading" };
type NetworkFailedState = { state: "failed"; code: number };
type NetworkSuccessState = {
state: "success";
response: {
title: string;
duration: number;
summary: string;
};
};
// ---cut---
type NetworkFromCachedState = {
state: "from_cache";
id: string;
response: NetworkSuccessState["response"];
};

type NetworkState =
| NetworkLoadingState
| NetworkFailedState
| NetworkSuccessState
| NetworkFromCachedState;

function logger(s: NetworkState) {


switch (s.state) {
case "loading":
return "loading request";
case "failed":
return `failed with code ${s.code}`;

137
typescript

case "success":
return "got response";
}
}

There are two ways to do this. The first is to turn on strictNullChecks and specify a return type:

// @errors: 2366
type NetworkLoadingState = { state: "loading" };
type NetworkFailedState = { state: "failed"; code: number };
type NetworkSuccessState = { state: "success" };
type NetworkFromCachedState = { state: "from_cache" };

type NetworkState =
| NetworkLoadingState
| NetworkFailedState
| NetworkSuccessState
| NetworkFromCachedState;

// ---cut---
function logger(s: NetworkState): string {
switch (s.state) {
case "loading":
return "loading request";
case "failed":
return `failed with code ${s.code}`;
case "success":
return "got response";
}
}

Because the switch is no longer exhaustive, TypeScript is aware that the function could sometimes return
undefined . If you have an explicit return type string , then you will get an error that the return type is
actually string | undefined . However, this method is quite subtle and, besides, strictNullChecks
does not always work with old code.

The second method uses the never type that the compiler uses to check for exhaustiveness:

// @errors: 2345
type NetworkLoadingState = { state: "loading" };
type NetworkFailedState = { state: "failed"; code: number };
type NetworkSuccessState = { state: "success" };
type NetworkFromCachedState = { state: "from_cache" };

type NetworkState =
| NetworkLoadingState
| NetworkFailedState
| NetworkSuccessState
| NetworkFromCachedState;
// ---cut---
function assertNever(x: never): never {
throw new Error("Unexpected object: " + x);
}

function logger(s: NetworkState): string {


switch (s.state) {
case "loading":
return "loading request";
case "failed":

138
Unions and Intersection Types

return `failed with code ${s.code}`;


case "success":
return "got response";
default:
return assertNever(s);
}
}

Here, assertNever checks that s is of type never — the type that's left after all other cases have been
removed. If you forget a case, then s will have a real type and you will get a type error. This method re-
quires you to define an extra function, but it's much more obvious when you forget it because the error
message includes the missing type name.

Intersection Types
Intersection types are closely related to union types, but they are used very differently. An intersection type
combines multiple types into one. This allows you to add together existing types to get a single type that
has all the features you need. For example, Person & Serializable & Loggable is a type which is all of
Person and Serializable and Loggable . That means an object of this type will have all members of all
three types.

For example, if you had networking requests with consistent error handling then you could separate out the
error handling into its own type which is merged with types which correspond to a single response type.

interface ErrorHandling {
success: boolean;
error?: { message: string };
}

interface ArtworksData {
artworks: { title: string }[];
}

interface ArtistsData {
artists: { name: string }[];
}

// These interfaces are composed to have


// consistent error handling, and their own data.

type ArtworksResponse = ArtworksData & ErrorHandling;


type ArtistsResponse = ArtistsData & ErrorHandling;

const handleArtistsResponse = (response: ArtistsResponse) => {


if (response.error) {
console.error(response.error.message);
return;
}

console.log(response.artists);
};

Go to TOC

139
typescript

Each and every value in JavaScript has a set of behaviors you can observe from running different
operations. That sounds abstract, but as a quick example, consider some operations we might run on a vari-
able named message .

// Accessing the property 'toLowerCase'


// on 'message' and then calling it
message.toLowerCase();

// Calling 'message'
message();

If we break this down, the first runnable line of code accesses a property called toLowerCase and then
calls it. The second one tries to call message directly.

But assuming we don't know the value of message - and that's pretty common - we can't reliably say what
results we'll get from trying to run any of this code. The behavior of each operation depends entirely on
what value we had in the first place.

Is message callable?
Does it have a property called toLowerCase on it?
If it does, is toLowerCase even callable?
If both of these values are callable, what do they return?

The answers to these questions are usually things we keep in our heads when we write JavaScript, and we
have to hope we got all the details right.

Let's say message was defined in the following way.

const message = "Hello World!";

As you can probably guess, if we try to run message.toLowerCase() , we'll get the same string only in low-
er-case.

What about that second line of code? If you're familiar with JavaScript, you'll know this fails with an
exception:

TypeError: message is not a function

It'd be great if we could avoid mistakes like this.

When we run our code, the way that our JavaScript runtime chooses what to do is by figuring out the type
of the value - what sorts of behaviors and capabilities it has. That's part of what that TypeError is alluding
to - it's saying that the string "Hello World!" cannot be called as a function.

For some values, such as the primitives string and number , we can identify their type at runtime using
the typeof operator. But for other things like functions, there's no corresponding runtime mechanism to
identify their types. For example, consider this function:

140
The Basics

function fn(x) {
return x.flip();
}

We can observe by reading the code that this function will only work if given an object with a callable flip
property, but JavaScript doesn't surface this information in a way that we can check while the code is run-
ning. The only way in pure JavaScript to tell what fn does with a particular value is to call it and see what
happens. This kind of behavior makes it hard to predict what code will do before it runs, which means it's
harder to know what your code is going to do while you're writing it.

Seen in this way, a type is the concept of describing which values can be passed to fn and which will
crash. JavaScript only truly provides dynamic typing - running the code to see what happens.

The alternative is to use a static type system to make predictions about what code is expected before it
runs.

Static type-checking
Think back to that TypeError we got earlier from trying to call a string as a function. Most people don't
like to get any sorts of errors when running their code - those are considered bugs! And when we write new
code, we try our best to avoid introducing new bugs.

If we add just a bit of code, save our file, re-run the code, and immediately see the error, we might be able
to isolate the problem quickly; but that's not always the case. We might not have tested the feature thor-
oughly enough, so we might never actually run into a potential error that would be thrown! Or if we were
lucky enough to witness the error, we might have ended up doing large refactorings and adding a lot of dif-
ferent code that we're forced to dig through.

Ideally, we could have a tool that helps us find these bugs before our code runs. That's what a static type-
checker like TypeScript does. Static types systems describe the shapes and behaviors of what our values
will be when we run our programs. A type-checker like TypeScript uses that information and tells us when
things might be going off the rails.

// @errors: 2349
const message = "hello!";

message();

Running that last sample with TypeScript will give us an error message before we run the code in the first
place.

Non-exception Failures
So far we've been discussing certain things like runtime errors - cases where the JavaScript runtime tells us
that it thinks something is nonsensical. Those cases come up because the ECMAScript specification has ex-
plicit instructions on how the language should behave when it runs into something unexpected.

141
typescript

For example, the specification says that trying to call something that isn't callable should throw an error.
Maybe that sounds like "obvious behavior", but you could imagine that accessing a property that doesn't ex-
ist on an object should throw an error too. Instead, JavaScript gives us different behavior and returns the
value undefined :

const user = {
name: "Daniel",
age: 26,
};

user.location; // returns undefined

Ultimately, a static type system has to make the call over what code should be flagged as an error in its
system, even if it's "valid" JavaScript that won't immediately throw an error. In TypeScript, the following
code produces an error about location not being defined:

// @errors: 2339
const user = {
name: "Daniel",
age: 26,
};

user.location;

While sometimes that implies a trade-off in what you can express, the intent is to catch legitimate bugs in
our programs. And TypeScript catches a lot of legitimate bugs.

For example: typos,

// @noErrors
const announcement = "Hello World!";

// How quickly can you spot the typos?


announcement.toLocaleLowercase();
announcement.toLocalLowerCase();

// We probably meant to write this...


announcement.toLocaleLowerCase();

uncalled functions,

// @noUnusedLocals
// @errors: 2365
function flipCoin() {
// Meant to be Math.random()
return Math.random < 0.5;
}

or basic logic errors.

// @errors: 2367
const value = Math.random() < 0.5 ? "a" : "b";
if (value !== "a") {
// ...

142
The Basics

} else if (value === "b") {


// Oops, unreachable
}

Types for Tooling


TypeScript can catch bugs when we make mistakes in our code. That's great, but TypeScript can also pre-
vent us from making those mistakes in the first place.

The type-checker has information to check things like whether we're accessing the right properties on vari-
ables and other properties. Once it has that information, it can also start suggesting which properties you
might want to use.

That means TypeScript can be leveraged for editing code too, and the core type-checker can provide error
messages and code completion as you type in the editor. That's part of what people often refer to when
they talk about tooling in TypeScript.

// @noErrors
// @esModuleInterop
import express from "express";
const app = express();

app.get("/", function (req, res) {


res.sen
// ^|
});

app.listen(3000);

TypeScript takes tooling seriously, and that goes beyond completions and errors as you type. An editor that
supports TypeScript can deliver "quick fixes" to automatically fix errors, refactorings to easily re-organize
code, and useful navigation features for jumping to definitions of a variable, or finding all references to a
given variable. All of this is built on top of the type-checker and is fully cross-platform, so it's likely that
your favorite editor has TypeScript support available.

tsc , the TypeScript compiler


We've been talking about type-checking, but we haven't yet used our type-checker. Let's get acquainted
with our new friend tsc , the TypeScript compiler. First we'll need to grab it via npm.

npm install -g typescript

This installs the TypeScript Compiler tsc globally. You can use npx or similar tools if you'd prefer to
run tsc from a local node_modules package instead.

Now let's move to an empty folder and try writing our first TypeScript program: hello.ts :

// Greets the world.


console.log("Hello world!");

143
typescript

Notice there are no frills here; this "hello world" program looks identical to what you'd write for a "hello
world" program in JavaScript. And now let's type-check it by running the command tsc which was installed
for us by the typescript package.

tsc hello.ts

Tada!

Wait, "tada" what exactly? We ran tsc and nothing happened! Well, there were no type errors, so we
didn't get any output in our console since there was nothing to report.

But check again - we got some file output instead. If we look in our current directory, we'll see a hello.js
file next to hello.ts . That's the output from our hello.ts file after tsc compiles or transforms it into a
plain JavaScript file. And if we check the contents, we'll see what TypeScript spits out after it processes a
.ts file:

// Greets the world.


console.log("Hello world!");

In this case, there was very little for TypeScript to transform, so it looks identical to what we wrote. The
compiler tries to emit clean readable code that looks like something a person would write. While that's not
always so easy, TypeScript indents consistently, is mindful of when our code spans across different lines of
code, and tries to keep comments around.

What about if we did introduce a type-checking error? Let's rewrite hello.ts :

// @noErrors
// This is an industrial-grade general-purpose greeter function:
function greet(person, date) {
console.log(`Hello ${person}, today is ${date}!`);
}

greet("Brendan");

If we run tsc hello.ts again, notice that we get an error on the command line!

Expected 2 arguments, but got 1.

TypeScript is telling us we forgot to pass an argument to the greet function, and rightfully so. So far we've
only written standard JavaScript, and yet type-checking was still able to find problems with our code.
Thanks TypeScript!

Emitting with Errors


One thing you might not have noticed from the last example was that our hello.js file changed again. If
we open that file up then we'll see that the contents still basically look the same as our input file. That
might be a bit surprising given the fact that tsc reported an error about our code, but this is based on one
of TypeScript's core values: much of the time, you will know better than TypeScript.

144
The Basics

To reiterate from earlier, type-checking code limits the sorts of programs you can run, and so there's a
tradeoff on what sorts of things a type-checker finds acceptable. Most of the time that's okay, but there are
scenarios where those checks get in the way. For example, imagine yourself migrating JavaScript code over
to TypeScript and introducing type-checking errors. Eventually you'll get around to cleaning things up for
the type-checker, but that original JavaScript code was already working! Why should converting it over to
TypeScript stop you from running it?

So TypeScript doesn't get in your way. Of course, over time, you may want to be a bit more defensive
against mistakes, and make TypeScript act a bit more strictly. In that case, you can use the noEmitOn‐
Error compiler option. Try changing your hello.ts file and running tsc with that flag:

tsc --noEmitOnError hello.ts

You'll notice that hello.js never gets updated.

Explicit Types
Up until now, we haven't told TypeScript what person or date are. Let's edit the code to tell TypeScript
that person is a string , and that date should be a Date object. We'll also use the toDateString()
method on date .

function greet(person: string, date: Date) {


console.log(`Hello ${person}, today is ${date.toDateString()}!`);
}

What we did was add type annotations on person and date to describe what types of values greet can
be called with. You can read that signature as " greet takes a person of type string , and a date of
type Date ".

With this, TypeScript can tell us about other cases where greet might have been called incorrectly. For
example...

// @errors: 2345
function greet(person: string, date: Date) {
console.log(`Hello ${person}, today is ${date.toDateString()}!`);
}

greet("Maddison", Date());

Huh? TypeScript reported an error on our second argument, but why?

Perhaps surprisingly, calling Date() in JavaScript returns a string . On the other hand, constructing a
Date with new Date() actually gives us what we were expecting.

Anyway, we can quickly fix up the error:

function greet(person: string, date: Date) {


console.log(`Hello ${person}, today is ${date.toDateString()}!`);
}

greet("Maddison", new Date());

145
typescript

Keep in mind, we don't always have to write explicit type annotations. In many cases, TypeScript can even
just infer (or "figure out") the types for us even if we omit them.

let msg = "hello there!";


// ^?

Even though we didn't tell TypeScript that msg had the type string it was able to figure that out. That's a
feature, and it's best not to add annotations when the type system would end up inferring the same type
anyway.

Note: The message bubble inside the previous code sample is what your editor would show if you had
hovered over the word.

Erased Types
Let's take a look at what happens when we compile the above function greet with tsc to output
JavaScript:

// @showEmit
// @target: es5
function greet(person: string, date: Date) {
console.log(`Hello ${person}, today is ${date.toDateString()}!`);
}

greet("Maddison", new Date());

Notice two things here:

1. Our person and date parameters no longer have type annotations.


2. Our "template string" - that string that used backticks (the ` character) - was converted to plain strings
with concatenations.

More on that second point later, but let's now focus on that first point. Type annotations aren't part of
JavaScript (or ECMAScript to be pedantic), so there really aren't any browsers or other runtimes that can
just run TypeScript unmodified. That's why TypeScript needs a compiler in the first place - it needs some
way to strip out or transform any TypeScript-specific code so that you can run it. Most TypeScript-specific
code gets erased away, and likewise, here our type annotations were completely erased.

Remember: Type annotations never change the runtime behavior of your program.

Downleveling
One other difference from the above was that our template string was rewritten from

`Hello ${person}, today is ${date.toDateString()}!`;

146
The Basics

to

"Hello " + person + ", today is " + date.toDateString() + "!";

Why did this happen?

Template strings are a feature from a version of ECMAScript called ECMAScript 2015 (a.k.a. ECMAScript 6,
ES2015, ES6, etc. - don't ask). TypeScript has the ability to rewrite code from newer versions of ECMAScript
to older ones such as ECMAScript 3 or ECMAScript 5 (a.k.a. ES3 and ES5). This process of moving from a
newer or "higher" version of ECMAScript down to an older or "lower" one is sometimes called downleveling.

By default TypeScript targets ES3, an extremely old version of ECMAScript. We could have chosen some-
thing a little bit more recent by using the target option. Running with --target es2015 changes
TypeScript to target ECMAScript 2015, meaning code should be able to run wherever ECMAScript 2015 is
supported. So running tsc --target es2015 hello.ts gives us the following output:

function greet(person, date) {


console.log(`Hello ${person}, today is ${date.toDateString()}!`);
}
greet("Maddison", new Date());

While the default target is ES3, the great majority of current browsers support ES2015. Most develop-
ers can therefore safely specify ES2015 or above as a target, unless compatibility with certain ancient
browsers is important.

Strictness
Different users come to TypeScript looking for different things in a type-checker. Some people are looking
for a more loose opt-in experience which can help validate only some parts of their program, and still have
decent tooling. This is the default experience with TypeScript, where types are optional, inference takes the
most lenient types, and there's no checking for potentially null / undefined values. Much like how tsc
emits in the face of errors, these defaults are put in place to stay out of your way. If you're migrating exist-
ing JavaScript, that might be a desirable first step.

In contrast, a lot of users prefer to have TypeScript validate as much as it can straight away, and that's why
the language provides strictness settings as well. These strictness settings turn static type-checking from a
switch (either your code is checked or not) into something closer to a dial. The further you turn this dial up,
the more TypeScript will check for you. This can require a little extra work, but generally speaking it pays
for itself in the long run, and enables more thorough checks and more accurate tooling. When possible, a
new codebase should always turn these strictness checks on.

TypeScript has several type-checking strictness flags that can be turned on or off, and all of our examples
will be written with all of them enabled unless otherwise stated. The strict flag in the CLI, or "strict":
true in a tsconfig.json toggles them all on simultaneously, but we can opt out of them individually. The
two biggest ones you should know about are noImplicitAny and strictNullChecks .

147
typescript

noImplicitAny
Recall that in some places, TypeScript doesn't try to infer types for us and instead falls back to the most le-
nient type: any . This isn't the worst thing that can happen - after all, falling back to any is just the plain
JavaScript experience anyway.

However, using any often defeats the purpose of using TypeScript in the first place. The more typed your
program is, the more validation and tooling you'll get, meaning you'll run into fewer bugs as you code.
Turning on the noImplicitAny flag will issue an error on any variables whose type is implicitly inferred as
any .

strictNullChecks
By default, values like null and undefined are assignable to any other type. This can make writing some
code easier, but forgetting to handle null and undefined is the cause of countless bugs in the world -
some consider it a billion dollar mistake! The strictNullChecks flag makes handling null and unde‐
fined more explicit, and spares us from worrying about whether we forgot to handle null and unde‐
fined .

Go to TOC

148
Classes

Background Reading:
Classes (MDN)

TypeScript offers full support for the class keyword introduced in ES2015.

As with other JavaScript language features, TypeScript adds type annotations and other syntax to allow you
to express relationships between classes and other types.

Class Members
Here's the most basic class - an empty one:

class Point {}

This class isn't very useful yet, so let's start adding some members.

Fields
A field declaration creates a public writeable property on a class:

// @strictPropertyInitialization: false
class Point {
x: number;
y: number;
}

const pt = new Point();


pt.x = 0;
pt.y = 0;

As with other locations, the type annotation is optional, but will be an implicit any if not specified.

Fields can also have initializers; these will run automatically when the class is instantiated:

class Point {
x = 0;
y = 0;
}

const pt = new Point();


// Prints 0, 0
console.log(`${pt.x}, ${pt.y}`);

Just like with const , let , and var , the initializer of a class property will be used to infer its type:

// @errors: 2322
class Point {
x = 0;
y = 0;
}

149
typescript

// ---cut---
const pt = new Point();
pt.x = "0";

--strictPropertyInitialization

The strictPropertyInitialization setting controls whether class fields need to be initialized in the
constructor.

// @errors: 2564
class BadGreeter {
name: string;
}

class GoodGreeter {
name: string;

constructor() {
this.name = "hello";
}
}

Note that the field needs to be initialized in the constructor itself. TypeScript does not analyze methods you
invoke from the constructor to detect initializations, because a derived class might override those methods
and fail to initialize the members.

If you intend to definitely initialize a field through means other than the constructor (for example, maybe an
external library is filling in part of your class for you), you can use the definite assignment assertion opera-
tor, ! :

class OKGreeter {
// Not initialized, but no error
name!: string;
}

readonly
Fields may be prefixed with the readonly modifier. This prevents assignments to the field outside of the
constructor.

// @errors: 2540 2540


class Greeter {
readonly name: string = "world";

constructor(otherName?: string) {
if (otherName !== undefined) {
this.name = otherName;
}
}

err() {
this.name = "not ok";
}
}
const g = new Greeter();
g.name = "also not ok";

150
Classes

Constructors

Background Reading:
Constructor (MDN)

Class constructors are very similar to functions. You can add parameters with type annotations, default val-
ues, and overloads:

class Point {
x: number;
y: number;

// Normal signature with defaults


constructor(x = 0, y = 0) {
this.x = x;
this.y = y;
}
}

class Point {
// Overloads
constructor(x: number, y: string);
constructor(s: string);
constructor(xs: any, y?: any) {
// TBD
}
}

There are just a few differences between class constructor signatures and function signatures:

Constructors can't have type parameters - these belong on the outer class declaration, which we'll learn
about later
Constructors can't have return type annotations - the class instance type is always what's returned

Super Calls

Just as in JavaScript, if you have a base class, you'll need to call super(); in your constructor body before
using any this. members:

// @errors: 17009
class Base {
k = 4;
}

class Derived extends Base {


constructor() {
// Prints a wrong value in ES5; throws exception in ES6
console.log(this.k);
super();
}
}

151
typescript

Forgetting to call super is an easy mistake to make in JavaScript, but TypeScript will tell you when it's
necessary.

Methods

Background Reading:
Method definitions

A function property on a class is called a method. Methods can use all the same type annotations as func-
tions and constructors:

class Point {
x = 10;
y = 10;

scale(n: number): void {


this.x *= n;
this.y *= n;
}
}

Other than the standard type annotations, TypeScript doesn't add anything else new to methods.

Note that inside a method body, it is still mandatory to access fields and other methods via this. . An un-
qualified name in a method body will always refer to something in the enclosing scope:

// @errors: 2322
let x: number = 0;

class C {
x: string = "hello";

m() {
// This is trying to modify 'x' from line 1, not the class property
x = "world";
}
}

Getters / Setters
Classes can also have accessors:

class C {
_length = 0;
get length() {
return this._length;
}
set length(value) {
this._length = value;
}
}

152
Classes

Note that a field-backed get/set pair with no extra logic is very rarely useful in JavaScript. It's fine to
expose public fields if you don't need to add additional logic during the get/set operations.

TypeScript has some special inference rules for accessors:

If get exists but no set , the property is automatically readonly


If the type of the setter parameter is not specified, it is inferred from the return type of the getter
Getters and setters must have the same Member Visibility

Since TypeScript 4.3, it is possible to have accessors with different types for getting and setting.

class Thing {
_size = 0;

get size(): number {


return this._size;
}

set size(value: string | number | boolean) {


let num = Number(value);

// Don't allow NaN, Infinity, etc

if (!Number.isFinite(num)) {
this._size = 0;
return;
}

this._size = num;
}
}

Index Signatures
Classes can declare index signatures; these work the same as Index Signatures for other object types:

class MyClass {
[s: string]: boolean | ((s: string) => boolean);

check(s: string) {
return this[s] as boolean;
}
}

Because the index signature type needs to also capture the types of methods, it's not easy to usefully use
these types. Generally it's better to store indexed data in another place instead of on the class instance
itself.

Class Heritage
Like other languages with object-oriented features, classes in JavaScript can inherit from base classes.

153
typescript

implements Clauses
You can use an implements clause to check that a class satisfies a particular interface . An error will be
issued if a class fails to correctly implement it:

// @errors: 2420
interface Pingable {
ping(): void;
}

class Sonar implements Pingable {


ping() {
console.log("ping!");
}
}

class Ball implements Pingable {


pong() {
console.log("pong!");
}
}

Classes may also implement multiple interfaces, e.g. class C implements A, B { .

Cautions

It's important to understand that an implements clause is only a check that the class can be treated as the
interface type. It doesn't change the type of the class or its methods at all. A common source of error is to
assume that an implements clause will change the class type - it doesn't!

// @errors: 7006
interface Checkable {
check(name: string): boolean;
}

class NameChecker implements Checkable {


check(s) {
// Notice no error here
return s.toLowercse() === "ok";
// ^?
}
}

In this example, we perhaps expected that s 's type would be influenced by the name: string parameter
of check . It is not - implements clauses don't change how the class body is checked or its type inferred.

Similarly, implementing an interface with an optional property doesn't create that property:

// @errors: 2339
interface A {
x: number;
y?: number;
}
class C implements A {
x = 0;

154
Classes

}
const c = new C();
c.y = 10;

extends Clauses

Background Reading:
extends keyword (MDN)

Classes may extend from a base class. A derived class has all the properties and methods of its base
class, and also define additional members.

class Animal {
move() {
console.log("Moving along!");
}
}

class Dog extends Animal {


woof(times: number) {
for (let i = 0; i < times; i++) {
console.log("woof!");
}
}
}

const d = new Dog();


// Base class method
d.move();
// Derived class method
d.woof(3);

Overriding Methods

Background Reading:
super keyword (MDN)

A derived class can also override a base class field or property. You can use the super. syntax to access
base class methods. Note that because JavaScript classes are a simple lookup object, there is no notion of a
"super field".

TypeScript enforces that a derived class is always a subtype of its base class.

For example, here's a legal way to override a method:

class Base {
greet() {
console.log("Hello, world!");
}
}

155
typescript

class Derived extends Base {


greet(name?: string) {
if (name === undefined) {
super.greet();
} else {
console.log(`Hello, ${name.toUpperCase()}`);
}
}
}

const d = new Derived();


d.greet();
d.greet("reader");

It's important that a derived class follow its base class contract. Remember that it's very common (and al-
ways legal!) to refer to a derived class instance through a base class reference:

class Base {
greet() {
console.log("Hello, world!");
}
}
declare const d: Base;
// ---cut---
// Alias the derived instance through a base class reference
const b: Base = d;
// No problem
b.greet();

What if Derived didn't follow Base 's contract?

// @errors: 2416
class Base {
greet() {
console.log("Hello, world!");
}
}

class Derived extends Base {


// Make this parameter required
greet(name: string) {
console.log(`Hello, ${name.toUpperCase()}`);
}
}

If we compiled this code despite the error, this sample would then crash:

declare class Base {


greet(): void;
}
declare class Derived extends Base {}
// ---cut---
const b: Base = new Derived();
// Crashes because "name" will be undefined
b.greet();

156
Classes

Type-only Field Declarations

When target >= ES2022 or useDefineForClassFields is true , class fields are initialized after the par-
ent class constructor completes, overwriting any value set by the parent class. This can be a problem when
you only want to re-declare a more accurate type for an inherited field. To handle these cases, you can
write declare to indicate to TypeScript that there should be no runtime effect for this field declaration.

interface Animal {
dateOfBirth: any;
}

interface Dog extends Animal {


breed: any;
}

class AnimalHouse {
resident: Animal;
constructor(animal: Animal) {
this.resident = animal;
}
}

class DogHouse extends AnimalHouse {


// Does not emit JavaScript code,
// only ensures the types are correct
declare resident: Dog;
constructor(dog: Dog) {
super(dog);
}
}

Initialization Order

The order that JavaScript classes initialize can be surprising in some cases. Let's consider this code:

class Base {
name = "base";
constructor() {
console.log("My name is " + this.name);
}
}

class Derived extends Base {


name = "derived";
}

// Prints "base", not "derived"


const d = new Derived();

What happened here?

The order of class initialization, as defined by JavaScript, is:

The base class fields are initialized


The base class constructor runs
The derived class fields are initialized

157
typescript

The derived class constructor runs

This means that the base class constructor saw its own value for name during its own constructor, because
the derived class field initializations hadn't run yet.

Inheriting Built-in Types

Note: If you don't plan to inherit from built-in types like Array , Error , Map , etc. or your compila-
tion target is explicitly set to ES6 / ES2015 or above, you may skip this section

In ES2015, constructors which return an object implicitly substitute the value of this for any callers of
super(...) . It is necessary for generated constructor code to capture any potential return value of
super(...) and replace it with this .

As a result, subclassing Error , Array , and others may no longer work as expected. This is due to the fact
that constructor functions for Error , Array , and the like use ECMAScript 6's new.target to adjust the
prototype chain; however, there is no way to ensure a value for new.target when invoking a constructor
in ECMAScript 5. Other downlevel compilers generally have the same limitation by default.

For a subclass like the following:

class MsgError extends Error {


constructor(m: string) {
super(m);
}
sayHello() {
return "hello " + this.message;
}
}

you may find that:

methods may be undefined on objects returned by constructing these subclasses, so calling sayHello
will result in an error.
instanceof will be broken between instances of the subclass and their instances, so (new
MsgError()) instanceof MsgError will return false .

As a recommendation, you can manually adjust the prototype immediately after any super(...) calls.

class MsgError extends Error {


constructor(m: string) {
super(m);

// Set the prototype explicitly.


Object.setPrototypeOf(this, MsgError.prototype);
}

sayHello() {
return "hello " + this.message;
}
}

158
Classes

However, any subclass of MsgError will have to manually set the prototype as well. For runtimes that don't
support Object.setPrototypeOf , you may instead be able to use __proto__ .

Unfortunately, these workarounds will not work on Internet Explorer 10 and prior. One can manually copy
methods from the prototype onto the instance itself (i.e. MsgError.prototype onto this ), but the proto-
type chain itself cannot be fixed.

Member Visibility
You can use TypeScript to control whether certain methods or properties are visible to code outside the
class.

public
The default visibility of class members is public . A public member can be accessed anywhere:

class Greeter {
public greet() {
console.log("hi!");
}
}
const g = new Greeter();
g.greet();

Because public is already the default visibility modifier, you don't ever need to write it on a class member,
but might choose to do so for style/readability reasons.

protected
protected members are only visible to subclasses of the class they're declared in.

// @errors: 2445
class Greeter {
public greet() {
console.log("Hello, " + this.getName());
}
protected getName() {
return "hi";
}
}

class SpecialGreeter extends Greeter {


public howdy() {
// OK to access protected member here
console.log("Howdy, " + this.getName());
// ^^^^^^^^^^^^^^
}
}
const g = new SpecialGreeter();
g.greet(); // OK
g.getName();

159
typescript

Exposure of protected members

Derived classes need to follow their base class contracts, but may choose to expose a subtype of base class
with more capabilities. This includes making protected members public :

class Base {
protected m = 10;
}
class Derived extends Base {
// No modifier, so default is 'public'
m = 15;
}
const d = new Derived();
console.log(d.m); // OK

Note that Derived was already able to freely read and write m , so this doesn't meaningfully alter the "se-
curity" of this situation. The main thing to note here is that in the derived class, we need to be careful to re-
peat the protected modifier if this exposure isn't intentional.

Cross-hierarchy protected access

Different OOP languages disagree about whether it's legal to access a protected member through a base
class reference:

// @errors: 2446
class Base {
protected x: number = 1;
}
class Derived1 extends Base {
protected x: number = 5;
}
class Derived2 extends Base {
f1(other: Derived2) {
other.x = 10;
}
f2(other: Base) {
other.x = 10;
}
}

Java, for example, considers this to be legal. On the other hand, C# and C++ chose that this code should
be illegal.

TypeScript sides with C# and C++ here, because accessing x in Derived2 should only be legal from
Derived2 's subclasses, and Derived1 isn't one of them. Moreover, if accessing x through a Derived1
reference is illegal (which it certainly should be!), then accessing it through a base class reference should
never improve the situation.

See also Why Can’t I Access A Protected Member From A Derived Class? which explains more of C#'s
reasoning.

private
private is like protected , but doesn't allow access to the member even from subclasses:

160
Classes

// @errors: 2341
class Base {
private x = 0;
}
const b = new Base();
// Can't access from outside the class
console.log(b.x);

// @errors: 2341
class Base {
private x = 0;
}
// ---cut---
class Derived extends Base {
showX() {
// Can't access in subclasses
console.log(this.x);
}
}

Because private members aren't visible to derived classes, a derived class can't increase its visibility:

// @errors: 2415
class Base {
private x = 0;
}
class Derived extends Base {
x = 1;
}

Cross-instance private access

Different OOP languages disagree about whether different instances of the same class may access each oth-
ers' private members. While languages like Java, C#, C++, Swift, and PHP allow this, Ruby does not.

TypeScript does allow cross-instance private access:

class A {
private x = 10;

public sameAs(other: A) {
// No error
return other.x === this.x;
}
}

Caveats

Like other aspects of TypeScript's type system, private and protected are only enforced during type
checking.

This means that JavaScript runtime constructs like in or simple property lookup can still access a pri‐
vate or protected member:

class MySafe {
private secretKey = 12345;
}

161
typescript

// In a JavaScript file...
const s = new MySafe();
// Will print 12345
console.log(s.secretKey);

private also allows access using bracket notation during type checking. This makes private -declared
fields potentially easier to access for things like unit tests, with the drawback that these fields are soft pri-
vate and don't strictly enforce privacy.

// @errors: 2341
class MySafe {
private secretKey = 12345;
}

const s = new MySafe();

// Not allowed during type checking


console.log(s.secretKey);

// OK
console.log(s["secretKey"]);

Unlike TypeScripts's private , JavaScript's private fields ( # ) remain private after compilation and do not
provide the previously mentioned escape hatches like bracket notation access, making them hard private.

class Dog {
#barkAmount = 0;
personality = "happy";

constructor() {}
}

// @target: esnext
// @showEmit
class Dog {
#barkAmount = 0;
personality = "happy";

constructor() {}
}

When compiling to ES2021 or less, TypeScript will use WeakMaps in place of # .

// @target: es2015
// @showEmit
class Dog {
#barkAmount = 0;
personality = "happy";

constructor() {}
}

If you need to protect values in your class from malicious actors, you should use mechanisms that offer
hard runtime privacy, such as closures, WeakMaps, or private fields. Note that these added privacy checks
during runtime could affect performance.

162
Classes

Static Members

Background Reading:
Static Members (MDN)

Classes may have static members. These members aren't associated with a particular instance of the
class. They can be accessed through the class constructor object itself:

class MyClass {
static x = 0;
static printX() {
console.log(MyClass.x);
}
}
console.log(MyClass.x);
MyClass.printX();

Static members can also use the same public , protected , and private visibility modifiers:

// @errors: 2341
class MyClass {
private static x = 0;
}
console.log(MyClass.x);

Static members are also inherited:

class Base {
static getGreeting() {
return "Hello world";
}
}
class Derived extends Base {
myGreeting = Derived.getGreeting();
}

Special Static Names


It's generally not safe/possible to overwrite properties from the Function prototype. Because classes are
themselves functions that can be invoked with new , certain static names can't be used. Function proper-
ties like name , length , and call aren't valid to define as static members:

// @errors: 2699
class S {
static name = "S!";
}

Why No Static Classes?


TypeScript (and JavaScript) don't have a construct called static class the same way as, for example, C#
does.

163
typescript

Those constructs only exist because those languages force all data and functions to be inside a class; be-
cause that restriction doesn't exist in TypeScript, there's no need for them. A class with only a single in-
stance is typically just represented as a normal object in JavaScript/TypeScript.

For example, we don't need a "static class" syntax in TypeScript because a regular object (or even top-level
function) will do the job just as well:

// Unnecessary "static" class


class MyStaticClass {
static doSomething() {}
}

// Preferred (alternative 1)
function doSomething() {}

// Preferred (alternative 2)
const MyHelperObject = {
dosomething() {},
};

static Blocks in Classes


Static blocks allow you to write a sequence of statements with their own scope that can access private fields
within the containing class. This means that we can write initialization code with all the capabilities of writ-
ing statements, no leakage of variables, and full access to our class's internals.

declare function loadLastInstances(): any[]


// ---cut---
class Foo {
static #count = 0;

get count() {
return Foo.#count;
}

static {
try {
const lastInstances = loadLastInstances();
Foo.#count += lastInstances.length;
}
catch {}
}
}

Generic Classes
Classes, much like interfaces, can be generic. When a generic class is instantiated with new , its type para-
meters are inferred the same way as in a function call:

class Box<Type> {
contents: Type;
constructor(value: Type) {
this.contents = value;
}
}

164
Classes

const b = new Box("hello!");


// ^?

Classes can use generic constraints and defaults the same way as interfaces.

Type Parameters in Static Members


This code isn't legal, and it may not be obvious why:

// @errors: 2302
class Box<Type> {
static defaultValue: Type;
}

Remember that types are always fully erased! At runtime, there's only one Box.defaultValue property
slot. This means that setting Box<string>.defaultValue (if that were possible) would also change
Box<number>.defaultValue - not good. The static members of a generic class can never refer to the
class's type parameters.

this at Runtime in Classes

Background Reading:
this keyword (MDN)

It's important to remember that TypeScript doesn't change the runtime behavior of JavaScript, and that
JavaScript is somewhat famous for having some peculiar runtime behaviors.

JavaScript's handling of this is indeed unusual:

class MyClass {
name = "MyClass";
getName() {
return this.name;
}
}
const c = new MyClass();
const obj = {
name: "obj",
getName: c.getName,
};

// Prints "obj", not "MyClass"


console.log(obj.getName());

Long story short, by default, the value of this inside a function depends on how the function was called.
In this example, because the function was called through the obj reference, its value of this was obj
rather than the class instance.

This is rarely what you want to happen! TypeScript provides some ways to mitigate or prevent this kind of
error.

165
typescript

Arrow Functions

Background Reading:
Arrow functions (MDN)

If you have a function that will often be called in a way that loses its this context, it can make sense to
use an arrow function property instead of a method definition:

class MyClass {
name = "MyClass";
getName = () => {
return this.name;
};
}
const c = new MyClass();
const g = c.getName;
// Prints "MyClass" instead of crashing
console.log(g());

This has some trade-offs:

The this value is guaranteed to be correct at runtime, even for code not checked with TypeScript
This will use more memory, because each class instance will have its own copy of each function defined
this way
You can't use super.getName in a derived class, because there's no entry in the prototype chain to
fetch the base class method from

this parameters
In a method or function definition, an initial parameter named this has special meaning in TypeScript.
These parameters are erased during compilation:

type SomeType = any;


// ---cut---
// TypeScript input with 'this' parameter
function fn(this: SomeType, x: number) {
/* ... */
}

// JavaScript output
function fn(x) {
/* ... */
}

TypeScript checks that calling a function with a this parameter is done so with a correct context. Instead
of using an arrow function, we can add a this parameter to method definitions to statically enforce that
the method is called correctly:

// @errors: 2684
class MyClass {
name = "MyClass";

166
Classes

getName(this: MyClass) {
return this.name;
}
}
const c = new MyClass();
// OK
c.getName();

// Error, would crash


const g = c.getName;
console.log(g());

This method makes the opposite trade-offs of the arrow function approach:

JavaScript callers might still use the class method incorrectly without realizing it
Only one function per class definition gets allocated, rather than one per class instance
Base method definitions can still be called via super .

this Types
In classes, a special type called this refers dynamically to the type of the current class. Let's see how this
is useful:

class Box {
contents: string = "";
set(value: string) {
// ^?
this.contents = value;
return this;
}
}

Here, TypeScript inferred the return type of set to be this , rather than Box . Now let's make a subclass
of Box :

class Box {
contents: string = "";
set(value: string) {
this.contents = value;
return this;
}
}
// ---cut---
class ClearableBox extends Box {
clear() {
this.contents = "";
}
}

const a = new ClearableBox();


const b = a.set("hello");
// ^?

You can also use this in a parameter type annotation:

167
typescript

class Box {
content: string = "";
sameAs(other: this) {
return other.content === this.content;
}
}

This is different from writing other: Box -- if you have a derived class, its sameAs method will now only
accept other instances of that same derived class:

// @errors: 2345
class Box {
content: string = "";
sameAs(other: this) {
return other.content === this.content;
}
}

class DerivedBox extends Box {


otherContent: string = "?";
}

const base = new Box();


const derived = new DerivedBox();
derived.sameAs(base);

this -based type guards


You can use this is Type in the return position for methods in classes and interfaces. When mixed with a
type narrowing (e.g. if statements) the type of the target object would be narrowed to the specified
Type .

// @strictPropertyInitialization: false
class FileSystemObject {
isFile(): this is FileRep {
return this instanceof FileRep;
}
isDirectory(): this is Directory {
return this instanceof Directory;
}
isNetworked(): this is Networked & this {
return this.networked;
}
constructor(public path: string, private networked: boolean) {}
}

class FileRep extends FileSystemObject {


constructor(path: string, public content: string) {
super(path, false);
}
}

class Directory extends FileSystemObject {


children: FileSystemObject[];
}

interface Networked {
host: string;
}

168
Classes

const fso: FileSystemObject = new FileRep("foo/bar.txt", "foo");

if (fso.isFile()) {
fso.content;
// ^?
} else if (fso.isDirectory()) {
fso.children;
// ^?
} else if (fso.isNetworked()) {
fso.host;
// ^?
}

A common use-case for a this-based type guard is to allow for lazy validation of a particular field. For exam-
ple, this case removes an undefined from the value held inside box when hasValue has been verified to
be true:

class Box<T> {
value?: T;

hasValue(): this is { value: T } {


return this.value !== undefined;
}
}

const box = new Box();


box.value = "Gameboy";

box.value;
// ^?

if (box.hasValue()) {
box.value;
// ^?
}

Parameter Properties
TypeScript offers special syntax for turning a constructor parameter into a class property with the same
name and value. These are called parameter properties and are created by prefixing a constructor argument
with one of the visibility modifiers public , private , protected , or readonly . The resulting field gets
those modifier(s):

// @errors: 2341
class Params {
constructor(
public readonly x: number,
protected y: number,
private z: number
) {
// No body necessary
}
}
const a = new Params(1, 2, 3);
console.log(a.x);
// ^?
console.log(a.z);

169
typescript

Class Expressions

Background Reading:
Class expressions (MDN)

Class expressions are very similar to class declarations. The only real difference is that class expressions
don't need a name, though we can refer to them via whatever identifier they ended up bound to:

const someClass = class<Type> {


content: Type;
constructor(value: Type) {
this.content = value;
}
};

const m = new someClass("Hello, world");


// ^?

abstract Classes and Members


Classes, methods, and fields in TypeScript may be abstract.

An abstract method or abstract field is one that hasn't had an implementation provided. These members
must exist inside an abstract class, which cannot be directly instantiated.

The role of abstract classes is to serve as a base class for subclasses which do implement all the abstract
members. When a class doesn't have any abstract members, it is said to be concrete.

Let's look at an example:

// @errors: 2511
abstract class Base {
abstract getName(): string;

printName() {
console.log("Hello, " + this.getName());
}
}

const b = new Base();

We can't instantiate Base with new because it's abstract. Instead, we need to make a derived class and
implement the abstract members:

abstract class Base {


abstract getName(): string;
printName() {}
}
// ---cut---
class Derived extends Base {
getName() {
return "world";

170
Classes

}
}

const d = new Derived();


d.printName();

Notice that if we forget to implement the base class's abstract members, we'll get an error:

// @errors: 2515
abstract class Base {
abstract getName(): string;
printName() {}
}
// ---cut---
class Derived extends Base {
// forgot to do anything
}

Abstract Construct Signatures


Sometimes you want to accept some class constructor function that produces an instance of a class which
derives from some abstract class.

For example, you might want to write this code:

// @errors: 2511
abstract class Base {
abstract getName(): string;
printName() {}
}
class Derived extends Base {
getName() {
return "";
}
}
// ---cut---
function greet(ctor: typeof Base) {
const instance = new ctor();
instance.printName();
}

TypeScript is correctly telling you that you're trying to instantiate an abstract class. After all, given the defi-
nition of greet , it's perfectly legal to write this code, which would end up constructing an abstract class:

declare const greet: any, Base: any;


// ---cut---
// Bad!
greet(Base);

Instead, you want to write a function that accepts something with a construct signature:

// @errors: 2345
abstract class Base {
abstract getName(): string;
printName() {}
}
class Derived extends Base {

171
typescript

getName() {
return "";
}
}
// ---cut---
function greet(ctor: new () => Base) {
const instance = new ctor();
instance.printName();
}
greet(Derived);
greet(Base);

Now TypeScript correctly tells you about which class constructor functions can be invoked - Derived can
because it's concrete, but Base cannot.

Relationships Between Classes


In most cases, classes in TypeScript are compared structurally, the same as other types.

For example, these two classes can be used in place of each other because they're identical:

class Point1 {
x = 0;
y = 0;
}

class Point2 {
x = 0;
y = 0;
}

// OK
const p: Point1 = new Point2();

Similarly, subtype relationships between classes exist even if there's no explicit inheritance:

// @strict: false
class Person {
name: string;
age: number;
}

class Employee {
name: string;
age: number;
salary: number;
}

// OK
const p: Person = new Employee();

This sounds straightforward, but there are a few cases that seem stranger than others.

Empty classes have no members. In a structural type system, a type with no members is generally a super-
type of anything else. So if you write an empty class (don't!), anything can be used in place of it:

172
Classes

class Empty {}

function fn(x: Empty) {


// can't do anything with 'x', so I won't
}

// All OK!
fn(window);
fn({});
fn(fn);

Go to TOC

173
typescript

In this chapter, we'll cover some of the most common types of values you'll find in JavaScript code, and ex-
plain the corresponding ways to describe those types in TypeScript. This isn't an exhaustive list, and future
chapters will describe more ways to name and use other types.

Types can also appear in many more places than just type annotations. As we learn about the types them-
selves, we'll also learn about the places where we can refer to these types to form new constructs.

We'll start by reviewing the most basic and common types you might encounter when writing JavaScript or
TypeScript code. These will later form the core building blocks of more complex types.

The primitives: string , number , and boolean


JavaScript has three very commonly used primitives: string , number , and boolean . Each has a corre-
sponding type in TypeScript. As you might expect, these are the same names you'd see if you used the
JavaScript typeof operator on a value of those types:

string represents string values like "Hello, world"


number is for numbers like 42 . JavaScript does not have a special runtime value for integers, so there's
no equivalent to int or float - everything is simply number
boolean is for the two values true and false

The type names String , Number , and Boolean (starting with capital letters) are legal, but refer to
some special built-in types that will very rarely appear in your code. Always use string , number , or
boolean for types.

Arrays
To specify the type of an array like [1, 2, 3] , you can use the syntax number[] ; this syntax works for
any type (e.g. string[] is an array of strings, and so on). You may also see this written as
Array<number> , which means the same thing. We'll learn more about the syntax T<U> when we cover
generics.

Note that [number] is a different thing; refer to the section on Tuples.

any
TypeScript also has a special type, any , that you can use whenever you don't want a particular value to
cause typechecking errors.

174
Everyday Types

When a value is of type any , you can access any properties of it (which will in turn be of type any ), call it
like a function, assign it to (or from) a value of any type, or pretty much anything else that's syntactically
legal:

let obj: any = { x: 0 };


// None of the following lines of code will throw compiler errors.
// Using `any` disables all further type checking, and it is assumed
// you know the environment better than TypeScript.
obj.foo();
obj();
obj.bar = 100;
obj = "hello";
const n: number = obj;

The any type is useful when you don't want to write out a long type just to convince TypeScript that a par-
ticular line of code is okay.

noImplicitAny
When you don't specify a type, and TypeScript can't infer it from context, the compiler will typically default
to any .

You usually want to avoid this, though, because any isn't type-checked. Use the compiler flag noImplici‐
tAny to flag any implicit any as an error.

Type Annotations on Variables


When you declare a variable using const , var , or let , you can optionally add a type annotation to ex-
plicitly specify the type of the variable:

let myName: string = "Alice";


// ^^^^^^^^ Type annotation

TypeScript doesn't use "types on the left"-style declarations like int x = 0; Type annotations will al-
ways go after the thing being typed.

In most cases, though, this isn't needed. Wherever possible, TypeScript tries to automatically infer the
types in your code. For example, the type of a variable is inferred based on the type of its initializer:

// No type annotation needed -- 'myName' inferred as type 'string'


let myName = "Alice";

For the most part you don't need to explicitly learn the rules of inference. If you're starting out, try using
fewer type annotations than you think - you might be surprised how few you need for TypeScript to fully un-
derstand what's going on.

175
typescript

Functions
Functions are the primary means of passing data around in JavaScript. TypeScript allows you to specify the
types of both the input and output values of functions.

Parameter Type Annotations


When you declare a function, you can add type annotations after each parameter to declare what types of
parameters the function accepts. Parameter type annotations go after the parameter name:

// Parameter type annotation


function greet(name: string) {
// ^^^^^^^^
console.log("Hello, " + name.toUpperCase() + "!!");
}

When a parameter has a type annotation, arguments to that function will be checked:

// @errors: 2345
declare function greet(name: string): void;
// ---cut---
// Would be a runtime error if executed!
greet(42);

Even if you don't have type annotations on your parameters, TypeScript will still check that you
passed the right number of arguments.

Return Type Annotations


You can also add return type annotations. Return type annotations appear after the parameter list:

function getFavoriteNumber(): number {


// ^^^^^^^^
return 26;
}

Much like variable type annotations, you usually don't need a return type annotation because TypeScript will
infer the function's return type based on its return statements. The type annotation in the above example
doesn't change anything. Some codebases will explicitly specify a return type for documentation purposes,
to prevent accidental changes, or just for personal preference.

Anonymous Functions
Anonymous functions are a little bit different from function declarations. When a function appears in a place
where TypeScript can determine how it's going to be called, the parameters of that function are automati-
cally given types.

Here's an example:

176
Everyday Types

// @errors: 2551
// No type annotations here, but TypeScript can spot the bug
const names = ["Alice", "Bob", "Eve"];

// Contextual typing for function


names.forEach(function (s) {
console.log(s.toUppercase());
});

// Contextual typing also applies to arrow functions


names.forEach((s) => {
console.log(s.toUppercase());
});

Even though the parameter s didn't have a type annotation, TypeScript used the types of the forEach
function, along with the inferred type of the array, to determine the type s will have.

This process is called contextual typing because the context that the function occurred within informs what
type it should have.

Similar to the inference rules, you don't need to explicitly learn how this happens, but understanding that it
does happen can help you notice when type annotations aren't needed. Later, we'll see more examples of
how the context that a value occurs in can affect its type.

Object Types
Apart from primitives, the most common sort of type you'll encounter is an object type. This refers to any
JavaScript value with properties, which is almost all of them! To define an object type, we simply list its
properties and their types.

For example, here's a function that takes a point-like object:

// The parameter's type annotation is an object type


function printCoord(pt: { x: number; y: number }) {
// ^^^^^^^^^^^^^^^^^^^^^^^^
console.log("The coordinate's x value is " + pt.x);
console.log("The coordinate's y value is " + pt.y);
}
printCoord({ x: 3, y: 7 });

Here, we annotated the parameter with a type with two properties - x and y - which are both of type
number . You can use , or ; to separate the properties, and the last separator is optional either way.

The type part of each property is also optional. If you don't specify a type, it will be assumed to be any .

Optional Properties
Object types can also specify that some or all of their properties are optional. To do this, add a ? after the
property name:

function printName(obj: { first: string; last?: string }) {


// ...
}

177
typescript

// Both OK
printName({ first: "Bob" });
printName({ first: "Alice", last: "Alisson" });

In JavaScript, if you access a property that doesn't exist, you'll get the value undefined rather than a run-
time error. Because of this, when you read from an optional property, you'll have to check for undefined
before using it.

// @errors: 2532
function printName(obj: { first: string; last?: string }) {
// Error - might crash if 'obj.last' wasn't provided!
console.log(obj.last.toUpperCase());
if (obj.last !== undefined) {
// OK
console.log(obj.last.toUpperCase());
}

// A safe alternative using modern JavaScript syntax:


console.log(obj.last?.toUpperCase());
}

Union Types
TypeScript's type system allows you to build new types out of existing ones using a large variety of opera-
tors. Now that we know how to write a few types, it's time to start combining them in interesting ways.

Defining a Union Type


The first way to combine types you might see is a union type. A union type is a type formed from two or
more other types, representing values that may be any one of those types. We refer to each of these types
as the union's members.

Let's write a function that can operate on strings or numbers:

// @errors: 2345
function printId(id: number | string) {
console.log("Your ID is: " + id);
}
// OK
printId(101);
// OK
printId("202");
// Error
printId({ myID: 22342 });

Working with Union Types


It's easy to provide a value matching a union type - simply provide a type matching any of the union's
members. If you have a value of a union type, how do you work with it?

TypeScript will only allow an operation if it is valid for every member of the union. For example, if you have
the union string | number , you can't use methods that are only available on string :

178
Everyday Types

// @errors: 2339
function printId(id: number | string) {
console.log(id.toUpperCase());
}

The solution is to narrow the union with code, the same as you would in JavaScript without type
annotations. Narrowing occurs when TypeScript can deduce a more specific type for a value based on the
structure of the code.

For example, TypeScript knows that only a string value will have a typeof value "string" :

function printId(id: number | string) {


if (typeof id === "string") {
// In this branch, id is of type 'string'
console.log(id.toUpperCase());
} else {
// Here, id is of type 'number'
console.log(id);
}
}

Another example is to use a function like Array.isArray :

function welcomePeople(x: string[] | string) {


if (Array.isArray(x)) {
// Here: 'x' is 'string[]'
console.log("Hello, " + x.join(" and "));
} else {
// Here: 'x' is 'string'
console.log("Welcome lone traveler " + x);
}
}

Notice that in the else branch, we don't need to do anything special - if x wasn't a string[] , then it
must have been a string .

Sometimes you'll have a union where all the members have something in common. For example, both ar-
rays and strings have a slice method. If every member in a union has a property in common, you can use
that property without narrowing:

// Return type is inferred as number[] | string


function getFirstThree(x: number[] | string) {
return x.slice(0, 3);
}

It might be confusing that a union of types appears to have the intersection of those types'
properties. This is not an accident - the name union comes from type theory. The union number | st
ring is composed by taking the union of the values from each type. Notice that given two sets with
corresponding facts about each set, only the intersection of those facts applies to the union of the sets

179
typescript

themselves. For example, if we had a room of tall people wearing hats, and another room of Spanish
speakers wearing hats, after combining those rooms, the only thing we know about every person is
that they must be wearing a hat.

Type Aliases
We've been using object types and union types by writing them directly in type annotations. This is conve-
nient, but it's common to want to use the same type more than once and refer to it by a single name.

A type alias is exactly that - a name for any type. The syntax for a type alias is:

type Point = {
x: number;
y: number;
};

// Exactly the same as the earlier example


function printCoord(pt: Point) {
console.log("The coordinate's x value is " + pt.x);
console.log("The coordinate's y value is " + pt.y);
}

printCoord({ x: 100, y: 100 });

You can actually use a type alias to give a name to any type at all, not just an object type. For example, a
type alias can name a union type:

type ID = number | string;

Note that aliases are only aliases - you cannot use type aliases to create different/distinct "versions" of the
same type. When you use the alias, it's exactly as if you had written the aliased type. In other words, this
code might look illegal, but is OK according to TypeScript because both types are aliases for the same type:

declare function getInput(): string;


declare function sanitize(str: string): string;
// ---cut---
type UserInputSanitizedString = string;

function sanitizeInput(str: string): UserInputSanitizedString {


return sanitize(str);
}

// Create a sanitized input


let userInput = sanitizeInput(getInput());

// Can still be re-assigned with a string though


userInput = "new input";

Interfaces
An interface declaration is another way to name an object type:

180
Everyday Types

interface Point {
x: number;
y: number;
}

function printCoord(pt: Point) {


console.log("The coordinate's x value is " + pt.x);
console.log("The coordinate's y value is " + pt.y);
}

printCoord({ x: 100, y: 100 });

Just like when we used a type alias above, the example works just as if we had used an anonymous object
type. TypeScript is only concerned with the structure of the value we passed to printCoord - it only cares
that it has the expected properties. Being concerned only with the structure and capabilities of types is why
we call TypeScript a structurally typed type system.

Differences Between Type Aliases and Interfaces


Type aliases and interfaces are very similar, and in many cases you can choose between them freely. Almost
all features of an interface are available in type , the key distinction is that a type cannot be re-opened
to add new properties vs an interface which is always extendable.

Interface Type

Extending an interface Extending a type via intersections

interface Animal { type Animal = {


name: string name: string
} }

interface Bear extends Animal { type Bear = Animal & {


honey: boolean honey: boolean
} }

const bear = getBear() const bear = getBear();


bear.name bear.name;
bear.honey bear.honey;

Adding new fields to an existing interface A type cannot be changed after being created

181
typescript

interface Window { type Window = {


title: string title: string
} }

interface Window { type Window = {


ts: TypeScriptAPI ts: TypeScriptAPI
} }

const src = 'const a = "Hello World"'; // Error: Duplicate identifier 'Window'.


window.ts.transpileModule(src, {});

You'll learn more about these concepts in later chapters, so don't worry if you don't understand all of these
right away.

Prior to TypeScript version 4.2, type alias names may appear in error messages, sometimes in place of
the equivalent anonymous type (which may or may not be desirable). Interfaces will always be named in
error messages.
Type aliases may not participate in declaration merging, but interfaces can.
Interfaces may only be used to declare the shapes of objects, not rename primitives.
Interface names will always appear in their original form in error messages, but only when they are used
by name.

For the most part, you can choose based on personal preference, and TypeScript will tell you if it needs
something to be the other kind of declaration. If you would like a heuristic, use interface until you need
to use features from type .

Type Assertions
Sometimes you will have information about the type of a value that TypeScript can't know about.

For example, if you're using document.getElementById , TypeScript only knows that this will return some
kind of HTMLElement , but you might know that your page will always have an HTMLCanvasElement with a
given ID.

In this situation, you can use a type assertion to specify a more specific type:

const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;

Like a type annotation, type assertions are removed by the compiler and won't affect the runtime behavior
of your code.

182
Everyday Types

You can also use the angle-bracket syntax (except if the code is in a .tsx file), which is equivalent:

const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas");

Reminder: Because type assertions are removed at compile-time, there is no runtime checking associ-
ated with a type assertion. There won't be an exception or null generated if the type assertion is
wrong.

TypeScript only allows type assertions which convert to a more specific or less specific version of a type.
This rule prevents "impossible" coercions like:

// @errors: 2352
const x = "hello" as number;

Sometimes this rule can be too conservative and will disallow more complex coercions that might be valid.
If this happens, you can use two assertions, first to any (or unknown , which we'll introduce later), then to
the desired type:

declare const expr: any;


type T = { a: 1; b: 2; c: 3 };
// ---cut---
const a = (expr as any) as T;

Literal Types
In addition to the general types string and number , we can refer to specific strings and numbers in type
positions.

One way to think about this is to consider how JavaScript comes with different ways to declare a variable.
Both var and let allow for changing what is held inside the variable, and const does not. This is reflect-
ed in how TypeScript creates types for literals.

let changingString = "Hello World";


changingString = "Olá Mundo";
// Because `changingString` can represent any possible string, that
// is how TypeScript describes it in the type system
changingString;
// ^?

const constantString = "Hello World";


// Because `constantString` can only represent 1 possible string, it
// has a literal type representation
constantString;
// ^?

By themselves, literal types aren't very valuable:

// @errors: 2322
let x: "hello" = "hello";
// OK

183
typescript

x = "hello";
// ...
x = "howdy";

It's not much use to have a variable that can only have one value!

But by combining literals into unions, you can express a much more useful concept - for example, functions
that only accept a certain set of known values:

// @errors: 2345
function printText(s: string, alignment: "left" | "right" | "center") {
// ...
}
printText("Hello, world", "left");
printText("G'day, mate", "centre");

Numeric literal types work the same way:

function compare(a: string, b: string): -1 | 0 | 1 {


return a === b ? 0 : a > b ? 1 : -1;
}

Of course, you can combine these with non-literal types:

// @errors: 2345
interface Options {
width: number;
}
function configure(x: Options | "auto") {
// ...
}
configure({ width: 100 });
configure("auto");
configure("automatic");

There's one more kind of literal type: boolean literals. There are only two boolean literal types, and as you
might guess, they are the types true and false . The type boolean itself is actually just an alias for the
union true | false .

Literal Inference
When you initialize a variable with an object, TypeScript assumes that the properties of that object might
change values later. For example, if you wrote code like this:

declare const someCondition: boolean;


// ---cut---
const obj = { counter: 0 };
if (someCondition) {
obj.counter = 1;
}

TypeScript doesn't assume the assignment of 1 to a field which previously had 0 is an error. Another way
of saying this is that obj.counter must have the type number , not 0 , because types are used to deter-
mine both reading and writing behavior.

184
Everyday Types

The same applies to strings:

// @errors: 2345
declare function handleRequest(url: string, method: "GET" | "POST"): void;
// ---cut---
const req = { url: "https://example.com", method: "GET" };
handleRequest(req.url, req.method);

In the above example req.method is inferred to be string , not "GET" . Because code can be evaluated
between the creation of req and the call of handleRequest which could assign a new string like "GUESS"
to req.method , TypeScript considers this code to have an error.

There are two ways to work around this.

1. You can change the inference by adding a type assertion in either location:

declare function handleRequest(url: string, method: "GET" | "POST"): void;


// ---cut---
// Change 1:
const req = { url: "https://example.com", method: "GET" as "GET" };
// Change 2
handleRequest(req.url, req.method as "GET");

Change 1 means "I intend for req.method to always have the literal type "GET" ", preventing the possi-
ble assignment of "GUESS" to that field after. Change 2 means "I know for other reasons that re‐
q.method has the value "GET" ".

2. You can use as const to convert the entire object to be type literals:

declare function handleRequest(url: string, method: "GET" | "POST"): void;


// ---cut---
const req = { url: "https://example.com", method: "GET" } as const;
handleRequest(req.url, req.method);

The as const suffix acts like const but for the type system, ensuring that all properties are assigned the
literal type instead of a more general version like string or number .

null and undefined


JavaScript has two primitive values used to signal absent or uninitialized value: null and undefined .

TypeScript has two corresponding types by the same names. How these types behave depends on whether
you have the strictNullChecks option on.

strictNullChecks off
With strictNullChecks off, values that might be null or undefined can still be accessed normally, and
the values null and undefined can be assigned to a property of any type. This is similar to how lan-
guages without null checks (e.g. C#, Java) behave. The lack of checking for these values tends to be a ma-
jor source of bugs; we always recommend people turn strictNullChecks on if it's practical to do so in
their codebase.

185
typescript

strictNullChecks on
With strictNullChecks on, when a value is null or undefined , you will need to test for those values
before using methods or properties on that value. Just like checking for undefined before using an option-
al property, we can use narrowing to check for values that might be null :

function doSomething(x: string | null) {


if (x === null) {
// do nothing
} else {
console.log("Hello, " + x.toUpperCase());
}
}

Non-null Assertion Operator (Postfix ! )


TypeScript also has a special syntax for removing null and undefined from a type without doing any ex-
plicit checking. Writing ! after any expression is effectively a type assertion that the value isn't null or
undefined :

function liveDangerously(x?: number | null) {


// No error
console.log(x!.toFixed());
}

Just like other type assertions, this doesn't change the runtime behavior of your code, so it's important to
only use ! when you know that the value can't be null or undefined .

Enums
Enums are a feature added to JavaScript by TypeScript which allows for describing a value which could be
one of a set of possible named constants. Unlike most TypeScript features, this is not a type-level addition
to JavaScript but something added to the language and runtime. Because of this, it's a feature which you
should know exists, but maybe hold off on using unless you are sure. You can read more about enums in
the Enum reference page.

Less Common Primitives


It's worth mentioning the rest of the primitives in JavaScript which are represented in the type system.
Though we will not go into depth here.

bigint

From ES2020 onwards, there is a primitive in JavaScript used for very large integers, BigInt :

// @target: es2020

// Creating a bigint via the BigInt function


const oneHundred: bigint = BigInt(100);

186
Everyday Types

// Creating a BigInt via the literal syntax


const anotherHundred: bigint = 100n;

You can learn more about BigInt in the TypeScript 3.2 release notes.

symbol

There is a primitive in JavaScript used to create a globally unique reference via the function Symbol() :

// @errors: 2367
const firstName = Symbol("name");
const secondName = Symbol("name");

if (firstName === secondName) {


// Can't ever happen
}

You can learn more about them in Symbols reference page.

Go to TOC

187
typescript

JavaScript has a long history of different ways to handle modularizing code. TypeScript having been around
since 2012, has implemented support for a lot of these formats, but over time the community and the
JavaScript specification has converged on a format called ES Modules (or ES6 modules). You might know it
as the import / export syntax.

ES Modules was added to the JavaScript spec in 2015, and by 2020 had broad support in most web
browsers and JavaScript runtimes.

For focus, the handbook will cover both ES Modules and its popular pre-cursor CommonJS module.exports
= syntax, and you can find information about the other module patterns in the reference section under
Modules.

How JavaScript Modules are Defined


In TypeScript, just as in ECMAScript 2015, any file containing a top-level import or export is considered
a module.

Conversely, a file without any top-level import or export declarations is treated as a script whose contents
are available in the global scope (and therefore to modules as well).

Modules are executed within their own scope, not in the global scope. This means that variables, functions,
classes, etc. declared in a module are not visible outside the module unless they are explicitly exported us-
ing one of the export forms. Conversely, to consume a variable, function, class, interface, etc. exported
from a different module, it has to be imported using one of the import forms.

Non-modules
Before we start, it's important to understand what TypeScript considers a module. The JavaScript specifica-
tion declares that any JavaScript files without an export or top-level await should be considered a script
and not a module.

Inside a script file variables and types are declared to be in the shared global scope, and it's assumed that
you'll either use the outFile compiler option to join multiple input files into one output file, or use multiple
<script> tags in your HTML to load these files (in the correct order!).

If you have a file that doesn't currently have any import s or export s, but you want to be treated as a
module, add the line:

export {};

which will change the file to be a module exporting nothing. This syntax works regardless of your module
target.

188
Modules

Modules in TypeScript

Additional Reading:
Impatient JS (Modules)
MDN: JavaScript Modules

There are three main things to consider when writing module-based code in TypeScript:

Syntax: What syntax do I want to use to import and export things?


Module Resolution: What is the relationship between module names (or paths) and files on disk?
Module Output Target: What should my emitted JavaScript module look like?

ES Module Syntax
A file can declare a main export via export default :

// @filename: hello.ts
export default function helloWorld() {
console.log("Hello, world!");
}

This is then imported via:

// @filename: hello.ts
export default function helloWorld() {
console.log("Hello, world!");
}
// @filename: index.ts
// ---cut---
import helloWorld from "./hello.js";
helloWorld();

In addition to the default export, you can have more than one export of variables and functions via the ex‐
port by omitting default :

// @filename: maths.ts
export var pi = 3.14;
export let squareTwo = 1.41;
export const phi = 1.61;

export class RandomNumberGenerator {}

export function absolute(num: number) {


if (num < 0) return num * -1;
return num;
}

These can be used in another file via the import syntax:

189
typescript

// @filename: maths.ts
export var pi = 3.14;
export let squareTwo = 1.41;
export const phi = 1.61;
export class RandomNumberGenerator {}
export function absolute(num: number) {
if (num < 0) return num * -1;
return num;
}
// @filename: app.ts
// ---cut---
import { pi, phi, absolute } from "./maths.js";

console.log(pi);
const absPhi = absolute(phi);
// ^?

Additional Import Syntax


An import can be renamed using a format like import {old as new} :

// @filename: maths.ts
export var pi = 3.14;
// @filename: app.ts
// ---cut---
import { pi as π } from "./maths.js";

console.log(π);
// ^?

You can mix and match the above syntax into a single import :

// @filename: maths.ts
export const pi = 3.14;
export default class RandomNumberGenerator {}

// @filename: app.ts
import RandomNumberGenerator, { pi as π } from "./maths.js";

RandomNumberGenerator;
// ^?

console.log(π);
// ^?

You can take all of the exported objects and put them into a single namespace using * as name :

// @filename: maths.ts
export var pi = 3.14;
export let squareTwo = 1.41;
export const phi = 1.61;

export function absolute(num: number) {


if (num < 0) return num * -1;
return num;
}
// ---cut---
// @filename: app.ts
import * as math from "./maths.js";

190
Modules

console.log(math.pi);
const positivePhi = math.absolute(math.phi);
// ^?

You can import a file and not include any variables into your current module via import "./file" :

// @filename: maths.ts
export var pi = 3.14;
// ---cut---
// @filename: app.ts
import "./maths.js";

console.log("3.14");

In this case, the import does nothing. However, all of the code in maths.ts was evaluated, which could
trigger side-effects which affect other objects.

TypeScript Specific ES Module Syntax

Types can be exported and imported using the same syntax as JavaScript values:

// @filename: animal.ts
export type Cat = { breed: string; yearOfBirth: number };

export interface Dog {


breeds: string[];
yearOfBirth: number;
}

// @filename: app.ts
import { Cat, Dog } from "./animal.js";
type Animals = Cat | Dog;

TypeScript has extended the import syntax with two concepts for declaring an import of a type:

import type

Which is an import statement which can only import types:

// @filename: animal.ts
export type Cat = { breed: string; yearOfBirth: number };
export type Dog = { breeds: string[]; yearOfBirth: number };
export const createCatName = () => "fluffy";

// @filename: valid.ts
import type { Cat, Dog } from "./animal.js";
export type Animals = Cat | Dog;

// @filename: app.ts
// @errors: 1361
import type { createCatName } from "./animal.js";
const name = createCatName();

Inline type imports

TypeScript 4.5 also allows for individual imports to be prefixed with type to indicate that the imported ref-
erence is a type:

191
typescript

// @filename: animal.ts
export type Cat = { breed: string; yearOfBirth: number };
export type Dog = { breeds: string[]; yearOfBirth: number };
export const createCatName = () => "fluffy";
// ---cut---
// @filename: app.ts
import { createCatName, type Cat, type Dog } from "./animal.js";

export type Animals = Cat | Dog;


const name = createCatName();

Together these allow a non-TypeScript transpiler like Babel, swc or esbuild to know what imports can be
safely removed.

ES Module Syntax with CommonJS Behavior

TypeScript has ES Module syntax which directly correlates to a CommonJS and AMD require . Imports us-
ing ES Module are for most cases the same as the require from those environments, but this syntax en-
sures you have a 1 to 1 match in your TypeScript file with the CommonJS output:

/// <reference types="node" />


// @module: commonjs
// ---cut---
import fs = require("fs");
const code = fs.readFileSync("hello.ts", "utf8");

You can learn more about this syntax in the modules reference page.

CommonJS Syntax
CommonJS is the format which most modules on npm are delivered in. Even if you are writing using the ES
Modules syntax above, having a brief understanding of how CommonJS syntax works will help you debug
easier.

Exporting

Identifiers are exported via setting the exports property on a global called module .

/// <reference types="node" />


// ---cut---
function absolute(num: number) {
if (num < 0) return num * -1;
return num;
}

module.exports = {
pi: 3.14,
squareTwo: 1.41,
phi: 1.61,
absolute,
};

Then these files can be imported via a require statement:

192
Modules

// @module: commonjs
// @filename: maths.ts
/// <reference types="node" />
function absolute(num: number) {
if (num < 0) return num * -1;
return num;
}

module.exports = {
pi: 3.14,
squareTwo: 1.41,
phi: 1.61,
absolute,
};
// @filename: index.ts
// ---cut---
const maths = require("maths");
maths.pi;
// ^?

Or you can simplify a bit using the destructuring feature in JavaScript:

// @module: commonjs
// @filename: maths.ts
/// <reference types="node" />
function absolute(num: number) {
if (num < 0) return num * -1;
return num;
}

module.exports = {
pi: 3.14,
squareTwo: 1.41,
phi: 1.61,
absolute,
};
// @filename: index.ts
// ---cut---
const { squareTwo } = require("maths");
squareTwo;
// ^?

CommonJS and ES Modules interop


There is a mis-match in features between CommonJS and ES Modules regarding the distinction between a
default import and a module namespace object import. TypeScript has a compiler flag to reduce the friction
between the two different sets of constraints with esModuleInterop .

TypeScript's Module Resolution Options


Module resolution is the process of taking a string from the import or require statement, and determin-
ing what file that string refers to.

TypeScript includes two resolution strategies: Classic and Node. Classic, the default when the compiler op-
tion module is not commonjs , is included for backwards compatibility. The Node strategy replicates how
Node.js works in CommonJS mode, with additional checks for .ts and .d.ts .

193
typescript

There are many TSConfig flags which influence the module strategy within TypeScript: moduleResolution ,
baseUrl , paths , rootDirs .

For the full details on how these strategies work, you can consult the Module Resolution.

TypeScript's Module Output Options


There are two options which affect the emitted JavaScript output:

target which determines which JS features are downleveled (converted to run in older JavaScript run-
times) and which are left intact
module which determines what code is used for modules to interact with each other

Which target you use is determined by the features available in the JavaScript runtime you expect to run
the TypeScript code in. That could be: the oldest web browser you support, the lowest version of Node.js
you expect to run on or could come from unique constraints from your runtime - like Electron for example.

All communication between modules happens via a module loader, the compiler option module determines
which one is used. At runtime the module loader is responsible for locating and executing all dependencies
of a module before executing it.

For example, here is a TypeScript file using ES Modules syntax, showcasing a few different options for mod‐
ule :

// @filename: constants.ts
export const valueOfPi = 3.142;
// @filename: index.ts
// ---cut---
import { valueOfPi } from "./constants.js";

export const twoPi = valueOfPi * 2;

ES2020

// @showEmit
// @module: es2020
// @noErrors
import { valueOfPi } from "./constants.js";

export const twoPi = valueOfPi * 2;

CommonJS

// @showEmit
// @module: commonjs
// @noErrors
import { valueOfPi } from "./constants.js";

export const twoPi = valueOfPi * 2;

194
Modules

UMD

// @showEmit
// @module: umd
// @noErrors
import { valueOfPi } from "./constants.js";

export const twoPi = valueOfPi * 2;

Note that ES2020 is effectively the same as the original index.ts .

You can see all of the available options and what their emitted JavaScript code looks like in the TSConfig
Reference for module .

TypeScript namespaces
TypeScript has its own module format called namespaces which pre-dates the ES Modules standard. This
syntax has a lot of useful features for creating complex definition files, and still sees active use in
DefinitelyTyped. While not deprecated, the majority of the features in namespaces exist in ES Modules and
we recommend you use that to align with JavaScript's direction. You can learn more about namespaces in
the namespaces reference page.

Go to TOC

195
typescript

Functions are the basic building block of any application, whether they're local functions, imported from an-
other module, or methods on a class. They're also values, and just like other values, TypeScript has many
ways to describe how functions can be called. Let's learn about how to write types that describe functions.

Function Type Expressions


The simplest way to describe a function is with a function type expression. These types are syntactically
similar to arrow functions:

function greeter(fn: (a: string) => void) {


fn("Hello, World");
}

function printToConsole(s: string) {


console.log(s);
}

greeter(printToConsole);

The syntax (a: string) => void means "a function with one parameter, named a , of type string, that
doesn't have a return value". Just like with function declarations, if a parameter type isn't specified, it's im-
plicitly any .

Note that the parameter name is required. The function type (string) => void means "a function
with a parameter named string of type any "!

Of course, we can use a type alias to name a function type:

type GreetFunction = (a: string) => void;


function greeter(fn: GreetFunction) {
// ...
}

Call Signatures
In JavaScript, functions can have properties in addition to being callable. However, the function type expres-
sion syntax doesn't allow for declaring properties. If we want to describe something callable with properties,
we can write a call signature in an object type:

type DescribableFunction = {
description: string;
(someArg: number): boolean;
};
function doSomething(fn: DescribableFunction) {
console.log(fn.description + " returned " + fn(6));
}

196
More on Functions

Note that the syntax is slightly different compared to a function type expression - use : between the para-
meter list and the return type rather than => .

Construct Signatures
JavaScript functions can also be invoked with the new operator. TypeScript refers to these as constructors
because they usually create a new object. You can write a construct signature by adding the new keyword
in front of a call signature:

type SomeObject = any;


// ---cut---
type SomeConstructor = {
new (s: string): SomeObject;
};
function fn(ctor: SomeConstructor) {
return new ctor("hello");
}

Some objects, like JavaScript's Date object, can be called with or without new . You can combine call and
construct signatures in the same type arbitrarily:

interface CallOrConstruct {
new (s: string): Date;
(n?: number): number;
}

Generic Functions
It's common to write a function where the types of the input relate to the type of the output, or where the
types of two inputs are related in some way. Let's consider for a moment a function that returns the first
element of an array:

function firstElement(arr: any[]) {


return arr[0];
}

This function does its job, but unfortunately has the return type any . It'd be better if the function returned
the type of the array element.

In TypeScript, generics are used when we want to describe a correspondence between two values. We do
this by declaring a type parameter in the function signature:

function firstElement<Type>(arr: Type[]): Type | undefined {


return arr[0];
}

By adding a type parameter Type to this function and using it in two places, we've created a link between
the input of the function (the array) and the output (the return value). Now when we call it, a more specific
type comes out:

197
typescript

declare function firstElement<Type>(arr: Type[]): Type | undefined;


// ---cut---
// s is of type 'string'
const s = firstElement(["a", "b", "c"]);
// n is of type 'number'
const n = firstElement([1, 2, 3]);
// u is of type undefined
const u = firstElement([]);

Inference
Note that we didn't have to specify Type in this sample. The type was inferred - chosen automatically - by
TypeScript.

We can use multiple type parameters as well. For example, a standalone version of map would look like
this:

// prettier-ignore
function map<Input, Output>(arr: Input[], func: (arg: Input) => Output): Output[]
{
return arr.map(func);
}

// Parameter 'n' is of type 'string'


// 'parsed' is of type 'number[]'
const parsed = map(["1", "2", "3"], (n) => parseInt(n));

Note that in this example, TypeScript could infer both the type of the Input type parameter (from the giv-
en string array), as well as the Output type parameter based on the return value of the function expres-
sion ( number ).

Constraints
We've written some generic functions that can work on any kind of value. Sometimes we want to relate two
values, but can only operate on a certain subset of values. In this case, we can use a constraint to limit the
kinds of types that a type parameter can accept.

Let's write a function that returns the longer of two values. To do this, we need a length property that's a
number. We constrain the type parameter to that type by writing an extends clause:

// @errors: 2345 2322


function longest<Type extends { length: number }>(a: Type, b: Type) {
if (a.length >= b.length) {
return a;
} else {
return b;
}
}

// longerArray is of type 'number[]'


const longerArray = longest([1, 2], [1, 2, 3]);
// longerString is of type 'alice' | 'bob'
const longerString = longest("alice", "bob");
// Error! Numbers don't have a 'length' property
const notOK = longest(10, 100);

198
More on Functions

There are a few interesting things to note in this example. We allowed TypeScript to infer the return type of
longest . Return type inference also works on generic functions.

Because we constrained Type to { length: number } , we were allowed to access the .length property
of the a and b parameters. Without the type constraint, we wouldn't be able to access those properties
because the values might have been some other type without a length property.

The types of longerArray and longerString were inferred based on the arguments. Remember, generics
are all about relating two or more values with the same type!

Finally, just as we'd like, the call to longest(10, 100) is rejected because the number type doesn't have
a .length property.

Working with Constrained Values


Here's a common error when working with generic constraints:

// @errors: 2322
function minimumLength<Type extends { length: number }>(
obj: Type,
minimum: number
): Type {
if (obj.length >= minimum) {
return obj;
} else {
return { length: minimum };
}
}

It might look like this function is OK - Type is constrained to { length: number } , and the function ei-
ther returns Type or a value matching that constraint. The problem is that the function promises to return
the same kind of object as was passed in, not just some object matching the constraint. If this code were
legal, you could write code that definitely wouldn't work:

declare function minimumLength<Type extends { length: number }>(


obj: Type,
minimum: number
): Type;
// ---cut---
// 'arr' gets value { length: 6 }
const arr = minimumLength([1, 2, 3], 6);
// and crashes here because arrays have
// a 'slice' method, but not the returned object!
console.log(arr.slice(0));

Specifying Type Arguments


TypeScript can usually infer the intended type arguments in a generic call, but not always. For example,
let's say you wrote a function to combine two arrays:

function combine<Type>(arr1: Type[], arr2: Type[]): Type[] {


return arr1.concat(arr2);
}

199
typescript

Normally it would be an error to call this function with mismatched arrays:

// @errors: 2322
declare function combine<Type>(arr1: Type[], arr2: Type[]): Type[];
// ---cut---
const arr = combine([1, 2, 3], ["hello"]);

If you intended to do this, however, you could manually specify Type :

declare function combine<Type>(arr1: Type[], arr2: Type[]): Type[];


// ---cut---
const arr = combine<string | number>([1, 2, 3], ["hello"]);

Guidelines for Writing Good Generic Functions


Writing generic functions is fun, and it can be easy to get carried away with type parameters. Having too
many type parameters or using constraints where they aren't needed can make inference less successful,
frustrating callers of your function.

Push Type Parameters Down

Here are two ways of writing a function that appear similar:

function firstElement1<Type>(arr: Type[]) {


return arr[0];
}

function firstElement2<Type extends any[]>(arr: Type) {


return arr[0];
}

// a: number (good)
const a = firstElement1([1, 2, 3]);
// b: any (bad)
const b = firstElement2([1, 2, 3]);

These might seem identical at first glance, but firstElement1 is a much better way to write this function.
Its inferred return type is Type , but firstElement2 's inferred return type is any because TypeScript has
to resolve the arr[0] expression using the constraint type, rather than "waiting" to resolve the element
during a call.

Rule: When possible, use the type parameter itself rather than constraining it

Use Fewer Type Parameters

Here's another pair of similar functions:

function filter1<Type>(arr: Type[], func: (arg: Type) => boolean): Type[] {


return arr.filter(func);
}

function filter2<Type, Func extends (arg: Type) => boolean>(

200
More on Functions

arr: Type[],
func: Func
): Type[] {
return arr.filter(func);
}

We've created a type parameter Func that doesn't relate two values. That's always a red flag, because it
means callers wanting to specify type arguments have to manually specify an extra type argument for no
reason. Func doesn't do anything but make the function harder to read and reason about!

Rule: Always use as few type parameters as possible

Type Parameters Should Appear Twice

Sometimes we forget that a function might not need to be generic:

function greet<Str extends string>(s: Str) {


console.log("Hello, " + s);
}

greet("world");

We could just as easily have written a simpler version:

function greet(s: string) {


console.log("Hello, " + s);
}

Remember, type parameters are for relating the types of multiple values. If a type parameter is only used
once in the function signature, it's not relating anything.

Rule: If a type parameter only appears in one location, strongly reconsider if you actually need it

Optional Parameters
Functions in JavaScript often take a variable number of arguments. For example, the toFixed method of
number takes an optional digit count:

function f(n: number) {


console.log(n.toFixed()); // 0 arguments
console.log(n.toFixed(3)); // 1 argument
}

We can model this in TypeScript by marking the parameter as optional with ? :

201
typescript

function f(x?: number) {


// ...
}
f(); // OK
f(10); // OK

Although the parameter is specified as type number , the x parameter will actually have the type number
| undefined because unspecified parameters in JavaScript get the value undefined .

You can also provide a parameter default:

function f(x = 10) {


// ...
}

Now in the body of f , x will have type number because any undefined argument will be replaced with
10 . Note that when a parameter is optional, callers can always pass undefined , as this simply simulates a
"missing" argument:

declare function f(x?: number): void;


// cut
// All OK
f();
f(10);
f(undefined);

Optional Parameters in Callbacks


Once you've learned about optional parameters and function type expressions, it's very easy to make the
following mistakes when writing functions that invoke callbacks:

function myForEach(arr: any[], callback: (arg: any, index?: number) => void) {
for (let i = 0; i < arr.length; i++) {
callback(arr[i], i);
}
}

What people usually intend when writing index? as an optional parameter is that they want both of these
calls to be legal:

// @errors: 2532
declare function myForEach(
arr: any[],
callback: (arg: any, index?: number) => void
): void;
// ---cut---
myForEach([1, 2, 3], (a) => console.log(a));
myForEach([1, 2, 3], (a, i) => console.log(a, i));

What this actually means is that callback might get invoked with one argument. In other words, the func-
tion definition says that the implementation might look like this:

202
More on Functions

// @errors: 2532
function myForEach(arr: any[], callback: (arg: any, index?: number) => void) {
for (let i = 0; i < arr.length; i++) {
// I don't feel like providing the index today
callback(arr[i]);
}
}

In turn, TypeScript will enforce this meaning and issue errors that aren't really possible:

// @errors: 2532
declare function myForEach(
arr: any[],
callback: (arg: any, index?: number) => void
): void;
// ---cut---
myForEach([1, 2, 3], (a, i) => {
console.log(i.toFixed());
});

In JavaScript, if you call a function with more arguments than there are parameters, the extra arguments
are simply ignored. TypeScript behaves the same way. Functions with fewer parameters (of the same types)
can always take the place of functions with more parameters.

When writing a function type for a callback, never write an optional parameter unless you intend to
call the function without passing that argument

Function Overloads
Some JavaScript functions can be called in a variety of argument counts and types. For example, you might
write a function to produce a Date that takes either a timestamp (one argument) or a month/day/year
specification (three arguments).

In TypeScript, we can specify a function that can be called in different ways by writing overload signatures.
To do this, write some number of function signatures (usually two or more), followed by the body of the
function:

// @errors: 2575
function makeDate(timestamp: number): Date;
function makeDate(m: number, d: number, y: number): Date;
function makeDate(mOrTimestamp: number, d?: number, y?: number): Date {
if (d !== undefined && y !== undefined) {
return new Date(y, mOrTimestamp, d);
} else {
return new Date(mOrTimestamp);
}
}
const d1 = makeDate(12345678);
const d2 = makeDate(5, 5, 5);
const d3 = makeDate(1, 3);

203
typescript

In this example, we wrote two overloads: one accepting one argument, and another accepting three argu-
ments. These first two signatures are called the overload signatures.

Then, we wrote a function implementation with a compatible signature. Functions have an implementation
signature, but this signature can't be called directly. Even though we wrote a function with two optional pa-
rameters after the required one, it can't be called with two parameters!

Overload Signatures and the Implementation Signature


This is a common source of confusion. Often people will write code like this and not understand why there is
an error:

// @errors: 2554
function fn(x: string): void;
function fn() {
// ...
}
// Expected to be able to call with zero arguments
fn();

Again, the signature used to write the function body can't be "seen" from the outside.

The signature of the implementation is not visible from the outside. When writing an overloaded func-
tion, you should always have two or more signatures above the implementation of the function.

The implementation signature must also be compatible with the overload signatures. For example, these
functions have errors because the implementation signature doesn't match the overloads in a correct way:

// @errors: 2394
function fn(x: boolean): void;
// Argument type isn't right
function fn(x: string): void;
function fn(x: boolean) {}

// @errors: 2394
function fn(x: string): string;
// Return type isn't right
function fn(x: number): boolean;
function fn(x: string | number) {
return "oops";
}

Writing Good Overloads


Like generics, there are a few guidelines you should follow when using function overloads. Following these
principles will make your function easier to call, easier to understand, and easier to implement.

Let's consider a function that returns the length of a string or an array:

204
More on Functions

function len(s: string): number;


function len(arr: any[]): number;
function len(x: any) {
return x.length;
}

This function is fine; we can invoke it with strings or arrays. However, we can't invoke it with a value that
might be a string or an array, because TypeScript can only resolve a function call to a single overload:

// @errors: 2769
declare function len(s: string): number;
declare function len(arr: any[]): number;
// ---cut---
len(""); // OK
len([0]); // OK
len(Math.random() > 0.5 ? "hello" : [0]);

Because both overloads have the same argument count and same return type, we can instead write a non-
overloaded version of the function:

function len(x: any[] | string) {


return x.length;
}

This is much better! Callers can invoke this with either sort of value, and as an added bonus, we don't have
to figure out a correct implementation signature.

Always prefer parameters with union types instead of overloads when possible

Declaring this in a Function


TypeScript will infer what the this should be in a function via code flow analysis, for example in the
following:

const user = {
id: 123,

admin: false,
becomeAdmin: function () {
this.admin = true;
},
};

TypeScript understands that the function user.becomeAdmin has a corresponding this which is the outer
object user . this , heh, can be enough for a lot of cases, but there are a lot of cases where you need
more control over what object this represents. The JavaScript specification states that you cannot have a
parameter called this , and so TypeScript uses that syntax space to let you declare the type for this in
the function body.

205
typescript

interface User {
id: number;
admin: boolean;
}
declare const getDB: () => DB;
// ---cut---
interface DB {
filterUsers(filter: (this: User) => boolean): User[];
}

const db = getDB();
const admins = db.filterUsers(function (this: User) {
return this.admin;
});

This pattern is common with callback-style APIs, where another object typically controls when your function
is called. Note that you need to use function and not arrow functions to get this behavior:

// @errors: 7041 7017


interface User {
id: number;
isAdmin: boolean;
}
declare const getDB: () => DB;
// ---cut---
interface DB {
filterUsers(filter: (this: User) => boolean): User[];
}

const db = getDB();
const admins = db.filterUsers(() => this.admin);

Other Types to Know About


There are some additional types you'll want to recognize that appear often when working with function
types. Like all types, you can use them everywhere, but these are especially relevant in the context of
functions.

void
void represents the return value of functions which don't return a value. It's the inferred type any time a
function doesn't have any return statements, or doesn't return any explicit value from those return
statements:

// The inferred return type is void


function noop() {
return;
}

In JavaScript, a function that doesn't return any value will implicitly return the value undefined . However,
void and undefined are not the same thing in TypeScript. There are further details at the end of this
chapter.

206
More on Functions

void is not the same as undefined .

object
The special type object refers to any value that isn't a primitive ( string , number , bigint , boolean ,
symbol , null , or undefined ). This is different from the empty object type { } , and also different from
the global type Object . It's very likely you will never use Object .

object is not Object . Always use object !

Note that in JavaScript, function values are objects: They have properties, have Object.prototype in their
prototype chain, are instanceof Object , you can call Object.keys on them, and so on. For this reason,
function types are considered to be object s in TypeScript.

unknown
The unknown type represents any value. This is similar to the any type, but is safer because it's not legal
to do anything with an unknown value:

// @errors: 2571
function f1(a: any) {
a.b(); // OK
}
function f2(a: unknown) {
a.b();
}

This is useful when describing function types because you can describe functions that accept any value with-
out having any values in your function body.

Conversely, you can describe a function that returns a value of unknown type:

declare const someRandomString: string;


// ---cut---
function safeParse(s: string): unknown {
return JSON.parse(s);
}

// Need to be careful with 'obj'!


const obj = safeParse(someRandomString);

never
Some functions never return a value:

function fail(msg: string): never {


throw new Error(msg);
}

207
typescript

The never type represents values which are never observed. In a return type, this means that the function
throws an exception or terminates execution of the program.

never also appears when TypeScript determines there's nothing left in a union.

function fn(x: string | number) {


if (typeof x === "string") {
// do something
} else if (typeof x === "number") {
// do something else
} else {
x; // has type 'never'!
}
}

Function
The global type Function describes properties like bind , call , apply , and others present on all func-
tion values in JavaScript. It also has the special property that values of type Function can always be
called; these calls return any :

function doSomething(f: Function) {


return f(1, 2, 3);
}

This is an untyped function call and is generally best avoided because of the unsafe any return type.

If you need to accept an arbitrary function but don't intend to call it, the type () => void is generally
safer.

Rest Parameters and Arguments

Background Reading:
Rest Parameters
Spread Syntax

Rest Parameters
In addition to using optional parameters or overloads to make functions that can accept a variety of fixed
argument counts, we can also define functions that take an unbounded number of arguments using rest
parameters.

A rest parameter appears after all other parameters, and uses the ... syntax:

function multiply(n: number, ...m: number[]) {


return m.map((x) => n * x);
}
// 'a' gets value [10, 20, 30, 40]
const a = multiply(10, 1, 2, 3, 4);

208
More on Functions

In TypeScript, the type annotation on these parameters is implicitly any[] instead of any , and any type
annotation given must be of the form Array<T> or T[] , or a tuple type (which we'll learn about later).

Rest Arguments
Conversely, we can provide a variable number of arguments from an array using the spread syntax. For ex-
ample, the push method of arrays takes any number of arguments:

const arr1 = [1, 2, 3];


const arr2 = [4, 5, 6];
arr1.push(...arr2);

Note that in general, TypeScript does not assume that arrays are immutable. This can lead to some surpris-
ing behavior:

// @errors: 2556
// Inferred type is number[] -- "an array with zero or more numbers",
// not specifically two numbers
const args = [8, 5];
const angle = Math.atan2(...args);

The best fix for this situation depends a bit on your code, but in general a const context is the most
straightforward solution:

// Inferred as 2-length tuple


const args = [8, 5] as const;
// OK
const angle = Math.atan2(...args);

Using rest arguments may require turning on downlevelIteration when targeting older runtimes.

Parameter Destructuring

Background Reading:
Destructuring Assignment

You can use parameter destructuring to conveniently unpack objects provided as an argument into one or
more local variables in the function body. In JavaScript, it looks like this:

function sum({ a, b, c }) {
console.log(a + b + c);
}
sum({ a: 10, b: 3, c: 9 });

The type annotation for the object goes after the destructuring syntax:

function sum({ a, b, c }: { a: number; b: number; c: number }) {


console.log(a + b + c);
}

209
typescript

This can look a bit verbose, but you can use a named type here as well:

// Same as prior example


type ABC = { a: number; b: number; c: number };
function sum({ a, b, c }: ABC) {
console.log(a + b + c);
}

Assignability of Functions
Return type void
The void return type for functions can produce some unusual, but expected behavior.

Contextual typing with a return type of void does not force functions to not return something. Another
way to say this is a contextual function type with a void return type ( type vf = () => void ), when im-
plemented, can return any other value, but it will be ignored.

Thus, the following implementations of the type () => void are valid:

type voidFunc = () => void;

const f1: voidFunc = () => {


return true;
};

const f2: voidFunc = () => true;

const f3: voidFunc = function () {


return true;
};

And when the return value of one of these functions is assigned to another variable, it will retain the type of
void :

type voidFunc = () => void;

const f1: voidFunc = () => {


return true;
};

const f2: voidFunc = () => true;

const f3: voidFunc = function () {


return true;
};
// ---cut---
const v1 = f1();

const v2 = f2();

const v3 = f3();

This behavior exists so that the following code is valid even though Array.prototype.push returns a num-
ber and the Array.prototype.forEach method expects a function with a return type of void .

210
More on Functions

const src = [1, 2, 3];


const dst = [0];

src.forEach((el) => dst.push(el));

There is one other special case to be aware of, when a literal function definition has a void return type,
that function must not return anything.

function f2(): void {


// @ts-expect-error
return true;
}

const f3 = function (): void {


// @ts-expect-error
return true;
};

For more on void please refer to these other documentation entries:

v1 handbook
v2 handbook
FAQ - "Why are functions returning non-void assignable to function returning void?"

Go to TOC

211
typescript

Imagine we have a function called padLeft .

function padLeft(padding: number | string, input: string): string {


throw new Error("Not implemented yet!");
}

If padding is a number , it will treat that as the number of spaces we want to prepend to input . If pad‐
ding is a string , it should just prepend padding to input . Let's try to implement the logic for when
padLeft is passed a number for padding .

// @errors: 2345
function padLeft(padding: number | string, input: string) {
return " ".repeat(padding) + input;
}

Uh-oh, we're getting an error on padding . TypeScript is warning us that adding a number | string to a
number might not give us what we want, and it's right. In other words, we haven't explicitly checked if
padding is a number first, nor are we handling the case where it's a string , so let's do exactly that.

function padLeft(padding: number | string, input: string) {


if (typeof padding === "number") {
return " ".repeat(padding) + input;
}
return padding + input;
}

If this mostly looks like uninteresting JavaScript code, that's sort of the point. Apart from the annotations
we put in place, this TypeScript code looks like JavaScript. The idea is that TypeScript's type system aims to
make it as easy as possible to write typical JavaScript code without bending over backwards to get type
safety.

While it might not look like much, there's actually a lot going under the covers here. Much like how
TypeScript analyzes runtime values using static types, it overlays type analysis on JavaScript's runtime con-
trol flow constructs like if/else , conditional ternaries, loops, truthiness checks, etc., which can all affect
those types.

Within our if check, TypeScript sees typeof padding === "number" and understands that as a special
form of code called a type guard. TypeScript follows possible paths of execution that our programs can take
to analyze the most specific possible type of a value at a given position. It looks at these special checks
(called type guards) and assignments, and the process of refining types to more specific types than de-
clared is called narrowing. In many editors we can observe these types as they change, and we'll even do
so in our examples.

function padLeft(padding: number | string, input: string) {


if (typeof padding === "number") {
return " ".repeat(padding) + input;
// ^?
}
return padding + input;
// ^?
}

212
Narrowing

There are a couple of different constructs TypeScript understands for narrowing.

typeof type guards


As we've seen, JavaScript supports a typeof operator which can give very basic information about the
type of values we have at runtime. TypeScript expects this to return a certain set of strings:

"string"
"number"
"bigint"
"boolean"
"symbol"
"undefined"
"object"
"function"

Like we saw with padLeft , this operator comes up pretty often in a number of JavaScript libraries, and
TypeScript can understand it to narrow types in different branches.

In TypeScript, checking against the value returned by typeof is a type guard. Because TypeScript encodes
how typeof operates on different values, it knows about some of its quirks in JavaScript. For example, no-
tice that in the list above, typeof doesn't return the string null . Check out the following example:

// @errors: 2531
function printAll(strs: string | string[] | null) {
if (typeof strs === "object") {
for (const s of strs) {
console.log(s);
}
} else if (typeof strs === "string") {
console.log(strs);
} else {
// do nothing
}
}

In the printAll function, we try to check if strs is an object to see if it's an array type (now might be a
good time to reinforce that arrays are object types in JavaScript). But it turns out that in JavaScript, type‐
of null is actually "object" ! This is one of those unfortunate accidents of history.

Users with enough experience might not be surprised, but not everyone has run into this in JavaScript;
luckily, TypeScript lets us know that strs was only narrowed down to string[] | null instead of just
string[] .

This might be a good segue into what we'll call "truthiness" checking.

213
typescript

Truthiness narrowing
Truthiness might not be a word you'll find in the dictionary, but it's very much something you'll hear about
in JavaScript.

In JavaScript, we can use any expression in conditionals, && s, || s, if statements, Boolean negations
( ! ), and more. As an example, if statements don't expect their condition to always have the type bool‐
ean .

function getUsersOnlineMessage(numUsersOnline: number) {


if (numUsersOnline) {
return `There are ${numUsersOnline} online now!`;
}
return "Nobody's here. :(";
}

In JavaScript, constructs like if first "coerce" their conditions to boolean s to make sense of them, and
then choose their branches depending on whether the result is true or false . Values like

0
NaN
"" (the empty string)
0n (the bigint version of zero)
null
undefined

all coerce to false , and other values get coerced true . You can always coerce values to boolean s by
running them through the Boolean function, or by using the shorter double-Boolean negation. (The latter
has the advantage that TypeScript infers a narrow literal boolean type true , while inferring the first as
type boolean .)

// both of these result in 'true'


Boolean("hello"); // type: boolean, value: true
!!"world"; // type: true, value: true

It's fairly popular to leverage this behavior, especially for guarding against values like null or undefined .
As an example, let's try using it for our printAll function.

function printAll(strs: string | string[] | null) {


if (strs && typeof strs === "object") {
for (const s of strs) {
console.log(s);
}
} else if (typeof strs === "string") {
console.log(strs);
}
}

You'll notice that we've gotten rid of the error above by checking if strs is truthy. This at least prevents us
from dreaded errors when we run our code like:

214
Narrowing

TypeError: null is not iterable

Keep in mind though that truthiness checking on primitives can often be error prone. As an example, con-
sider a different attempt at writing printAll

function printAll(strs: string | string[] | null) {


// !!!!!!!!!!!!!!!!
// DON'T DO THIS!
// KEEP READING
// !!!!!!!!!!!!!!!!
if (strs) {
if (typeof strs === "object") {
for (const s of strs) {
console.log(s);
}
} else if (typeof strs === "string") {
console.log(strs);
}
}
}

We wrapped the entire body of the function in a truthy check, but this has a subtle downside: we may no
longer be handling the empty string case correctly.

TypeScript doesn't hurt us here at all, but this is behavior worth noting if you're less familiar with
JavaScript. TypeScript can often help you catch bugs early on, but if you choose to do nothing with a value,
there's only so much that it can do without being overly prescriptive. If you want, you can make sure you
handle situations like these with a linter.

One last word on narrowing by truthiness is that Boolean negations with ! filter out from negated
branches.

function multiplyAll(
values: number[] | undefined,
factor: number
): number[] | undefined {
if (!values) {
return values;
} else {
return values.map((x) => x * factor);
}
}

Equality narrowing
TypeScript also uses switch statements and equality checks like === , !== , == , and != to narrow
types. For example:

function example(x: string | number, y: string | boolean) {


if (x === y) {
// We can now call any 'string' method on 'x' or 'y'.
x.toUpperCase();
// ^?
y.toLowerCase();
// ^?

215
typescript

} else {
console.log(x);
// ^?
console.log(y);
// ^?
}
}

When we checked that x and y are both equal in the above example, TypeScript knew their types also
had to be equal. Since string is the only common type that both x and y could take on, TypeScript
knows that x and y must be a string in the first branch.

Checking against specific literal values (as opposed to variables) works also. In our section about truthiness
narrowing, we wrote a printAll function which was error-prone because it accidentally didn't handle emp-
ty strings properly. Instead we could have done a specific check to block out null s, and TypeScript still
correctly removes null from the type of strs .

function printAll(strs: string | string[] | null) {


if (strs !== null) {
if (typeof strs === "object") {
for (const s of strs) {
// ^?
console.log(s);
}
} else if (typeof strs === "string") {
console.log(strs);
// ^?
}
}
}

JavaScript's looser equality checks with == and != also get narrowed correctly. If you're unfamiliar, check-
ing whether something == null actually not only checks whether it is specifically the value null - it also
checks whether it's potentially undefined . The same applies to == undefined : it checks whether a value
is either null or undefined .

interface Container {
value: number | null | undefined;
}

function multiplyValue(container: Container, factor: number) {


// Remove both 'null' and 'undefined' from the type.
if (container.value != null) {
console.log(container.value);
// ^?

// Now we can safely multiply 'container.value'.


container.value *= factor;
}
}

216
Narrowing

The in operator narrowing


JavaScript has an operator for determining if an object has a property with a name: the in operator.
TypeScript takes this into account as a way to narrow down potential types.

For example, with the code: "value" in x . where "value" is a string literal and x is a union type. The
"true" branch narrows x 's types which have either an optional or required property value , and the "false"
branch narrows to types which have an optional or missing property value .

type Fish = { swim: () => void };


type Bird = { fly: () => void };

function move(animal: Fish | Bird) {


if ("swim" in animal) {
return animal.swim();
}

return animal.fly();
}

To reiterate optional properties will exist in both sides for narrowing, for example a human could both swim
and fly (with the right equipment) and thus should show up in both sides of the in check:

type Fish = { swim: () => void };


type Bird = { fly: () => void };
type Human = { swim?: () => void; fly?: () => void };

function move(animal: Fish | Bird | Human) {


if ("swim" in animal) {
animal;
// ^?
} else {
animal;
// ^?
}
}

instanceof narrowing
JavaScript has an operator for checking whether or not a value is an "instance" of another value. More
specifically, in JavaScript x instanceof Foo checks whether the prototype chain of x contains
Foo.prototype . While we won't dive deep here, and you'll see more of this when we get into classes, they
can still be useful for most values that can be constructed with new . As you might have guessed, in‐
stanceof is also a type guard, and TypeScript narrows in branches guarded by instanceof s.

function logValue(x: Date | string) {


if (x instanceof Date) {
console.log(x.toUTCString());
// ^?
} else {
console.log(x.toUpperCase());
// ^?
}
}

217
typescript

Assignments
As we mentioned earlier, when we assign to any variable, TypeScript looks at the right side of the assign-
ment and narrows the left side appropriately.

let x = Math.random() < 0.5 ? 10 : "hello world!";


// ^?
x = 1;

console.log(x);
// ^?
x = "goodbye!";

console.log(x);
// ^?

Notice that each of these assignments is valid. Even though the observed type of x changed to number af-
ter our first assignment, we were still able to assign a string to x . This is because the declared type of
x - the type that x started with - is string | number , and assignability is always checked against the
declared type.

If we'd assigned a boolean to x , we'd have seen an error since that wasn't part of the declared type.

// @errors: 2322
let x = Math.random() < 0.5 ? 10 : "hello world!";
// ^?
x = 1;

console.log(x);
// ^?
x = true;

console.log(x);
// ^?

Control flow analysis


Up until this point, we've gone through some basic examples of how TypeScript narrows within specific
branches. But there's a bit more going on than just walking up from every variable and looking for type
guards in if s, while s, conditionals, etc. For example

function padLeft(padding: number | string, input: string) {


if (typeof padding === "number") {
return " ".repeat(padding) + input;
}
return padding + input;
}

padLeft returns from within its first if block. TypeScript was able to analyze this code and see that the
rest of the body ( return padding + input; ) is unreachable in the case where padding is a number . As
a result, it was able to remove number from the type of padding (narrowing from string | number to
string ) for the rest of the function.

218
Narrowing

This analysis of code based on reachability is called control flow analysis, and TypeScript uses this flow
analysis to narrow types as it encounters type guards and assignments. When a variable is analyzed, con-
trol flow can split off and re-merge over and over again, and that variable can be observed to have a differ-
ent type at each point.

function example() {
let x: string | number | boolean;

x = Math.random() < 0.5;

console.log(x);
// ^?

if (Math.random() < 0.5) {


x = "hello";
console.log(x);
// ^?
} else {
x = 100;
console.log(x);
// ^?
}

return x;
// ^?
}

Using type predicates


We've worked with existing JavaScript constructs to handle narrowing so far, however sometimes you want
more direct control over how types change throughout your code.

To define a user-defined type guard, we simply need to define a function whose return type is a type
predicate:

type Fish = { swim: () => void };


type Bird = { fly: () => void };
declare function getSmallPet(): Fish | Bird;
// ---cut---
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}

pet is Fish is our type predicate in this example. A predicate takes the form parameterName is Type ,
where parameterName must be the name of a parameter from the current function signature.

Any time isFish is called with some variable, TypeScript will narrow that variable to that specific type if
the original type is compatible.

type Fish = { swim: () => void };


type Bird = { fly: () => void };
declare function getSmallPet(): Fish | Bird;
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}

219
typescript

// ---cut---
// Both calls to 'swim' and 'fly' are now okay.
let pet = getSmallPet();

if (isFish(pet)) {
pet.swim();
} else {
pet.fly();
}

Notice that TypeScript not only knows that pet is a Fish in the if branch; it also knows that in the
else branch, you don't have a Fish , so you must have a Bird .

You may use the type guard isFish to filter an array of Fish | Bird and obtain an array of Fish :

type Fish = { swim: () => void; name: string };


type Bird = { fly: () => void; name: string };
declare function getSmallPet(): Fish | Bird;
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
// ---cut---
const zoo: (Fish | Bird)[] = [getSmallPet(), getSmallPet(), getSmallPet()];
const underWater1: Fish[] = zoo.filter(isFish);
// or, equivalently
const underWater2: Fish[] = zoo.filter(isFish) as Fish[];

// The predicate may need repeating for more complex examples


const underWater3: Fish[] = zoo.filter((pet): pet is Fish => {
if (pet.name === "sharkey") return false;
return isFish(pet);
});

In addition, classes can use this is Type to narrow their type.

Discriminated unions
Most of the examples we've looked at so far have focused around narrowing single variables with simple
types like string , boolean , and number . While this is common, most of the time in JavaScript we'll be
dealing with slightly more complex structures.

For some motivation, let's imagine we're trying to encode shapes like circles and squares. Circles keep track
of their radiuses and squares keep track of their side lengths. We'll use a field called kind to tell which
shape we're dealing with. Here's a first attempt at defining Shape .

interface Shape {
kind: "circle" | "square";
radius?: number;
sideLength?: number;
}

Notice we're using a union of string literal types: "circle" and "square" to tell us whether we should
treat the shape as a circle or square respectively. By using "circle" | "square" instead of string , we
can avoid misspelling issues.

220
Narrowing

// @errors: 2367
interface Shape {
kind: "circle" | "square";
radius?: number;
sideLength?: number;
}

// ---cut---
function handleShape(shape: Shape) {
// oops!
if (shape.kind === "rect") {
// ...
}
}

We can write a getArea function that applies the right logic based on if it's dealing with a circle or square.
We'll first try dealing with circles.

// @errors: 2532
interface Shape {
kind: "circle" | "square";
radius?: number;
sideLength?: number;
}

// ---cut---
function getArea(shape: Shape) {
return Math.PI * shape.radius ** 2;
}

Under strictNullChecks that gives us an error - which is appropriate since radius might not be
defined. But what if we perform the appropriate checks on the kind property?

// @errors: 2532
interface Shape {
kind: "circle" | "square";
radius?: number;
sideLength?: number;
}

// ---cut---
function getArea(shape: Shape) {
if (shape.kind === "circle") {
return Math.PI * shape.radius ** 2;
}
}

Hmm, TypeScript still doesn't know what to do here. We've hit a point where we know more about our val-
ues than the type checker does. We could try to use a non-null assertion (a ! after shape.radius ) to say
that radius is definitely present.

interface Shape {
kind: "circle" | "square";
radius?: number;
sideLength?: number;
}

// ---cut---

221
typescript

function getArea(shape: Shape) {


if (shape.kind === "circle") {
return Math.PI * shape.radius! ** 2;
}
}

But this doesn't feel ideal. We had to shout a bit at the type-checker with those non-null assertions ( ! ) to
convince it that shape.radius was defined, but those assertions are error-prone if we start to move code
around. Additionally, outside of strictNullChecks we're able to accidentally access any of those fields
anyway (since optional properties are just assumed to always be present when reading them). We can defi-
nitely do better.

The problem with this encoding of Shape is that the type-checker doesn't have any way to know whether
or not radius or sideLength are present based on the kind property. We need to communicate what
we know to the type checker. With that in mind, let's take another swing at defining Shape .

interface Circle {
kind: "circle";
radius: number;
}

interface Square {
kind: "square";
sideLength: number;
}

type Shape = Circle | Square;

Here, we've properly separated Shape out into two types with different values for the kind property, but
radius and sideLength are declared as required properties in their respective types.

Let's see what happens here when we try to access the radius of a Shape .

// @errors: 2339
interface Circle {
kind: "circle";
radius: number;
}

interface Square {
kind: "square";
sideLength: number;
}

type Shape = Circle | Square;

// ---cut---
function getArea(shape: Shape) {
return Math.PI * shape.radius ** 2;
}

222
Narrowing

Like with our first definition of Shape , this is still an error. When radius was optional, we got an error
(with strictNullChecks enabled) because TypeScript couldn't tell whether the property was present. Now
that Shape is a union, TypeScript is telling us that shape might be a Square , and Square s don't have
radius defined on them! Both interpretations are correct, but only the union encoding of Shape will cause
an error regardless of how strictNullChecks is configured.

But what if we tried checking the kind property again?

interface Circle {
kind: "circle";
radius: number;
}

interface Square {
kind: "square";
sideLength: number;
}

type Shape = Circle | Square;

// ---cut---
function getArea(shape: Shape) {
if (shape.kind === "circle") {
return Math.PI * shape.radius ** 2;
// ^?
}
}

That got rid of the error! When every type in a union contains a common property with literal types,
TypeScript considers that to be a discriminated union, and can narrow out the members of the union.

In this case, kind was that common property (which is what's considered a discriminant property of
Shape ). Checking whether the kind property was "circle" got rid of every type in Shape that didn't
have a kind property with the type "circle" . That narrowed shape down to the type Circle .

The same checking works with switch statements as well. Now we can try to write our complete getArea
without any pesky ! non-null assertions.

interface Circle {
kind: "circle";
radius: number;
}

interface Square {
kind: "square";
sideLength: number;
}

type Shape = Circle | Square;

// ---cut---
function getArea(shape: Shape) {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
// ^?

223
typescript

case "square":
return shape.sideLength ** 2;
// ^?
}
}

The important thing here was the encoding of Shape . Communicating the right information to TypeScript -
that Circle and Square were really two separate types with specific kind fields - was crucial. Doing that
let us write type-safe TypeScript code that looks no different than the JavaScript we would've written other-
wise. From there, the type system was able to do the "right" thing and figure out the types in each branch
of our switch statement.

As an aside, try playing around with the above example and remove some of the return keywords.
You'll see that type-checking can help avoid bugs when accidentally falling through different clauses in
a switch statement.

Discriminated unions are useful for more than just talking about circles and squares. They're good for repre-
senting any sort of messaging scheme in JavaScript, like when sending messages over the network
(client/server communication), or encoding mutations in a state management framework.

The never type


When narrowing, you can reduce the options of a union to a point where you have removed all possibilities
and have nothing left. In those cases, TypeScript will use a never type to represent a state which shouldn't
exist.

Exhaustiveness checking
The never type is assignable to every type; however, no type is assignable to never (except never
itself). This means you can use narrowing and rely on never turning up to do exhaustive checking in a
switch statement.

For example, adding a default to our getArea function which tries to assign the shape to never will
raise when every possible case has not been handled.

interface Circle {
kind: "circle";
radius: number;
}

interface Square {
kind: "square";
sideLength: number;
}
// ---cut---
type Shape = Circle | Square;

224
Narrowing

function getArea(shape: Shape) {


switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.sideLength ** 2;
default:
const _exhaustiveCheck: never = shape;
return _exhaustiveCheck;
}
}

Adding a new member to the Shape union, will cause a TypeScript error:

// @errors: 2322
interface Circle {
kind: "circle";
radius: number;
}

interface Square {
kind: "square";
sideLength: number;
}
// ---cut---
interface Triangle {
kind: "triangle";
sideLength: number;
}

type Shape = Circle | Square | Triangle;

function getArea(shape: Shape) {


switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.sideLength ** 2;
default:
const _exhaustiveCheck: never = shape;
return _exhaustiveCheck;
}
}

Go to TOC

225
typescript

In JavaScript, the fundamental way that we group and pass around data is through objects. In TypeScript,
we represent those through object types.

As we've seen, they can be anonymous:

function greet(person: { name: string; age: number }) {


// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
return "Hello " + person.name;
}

or they can be named by using either an interface

interface Person {
// ^^^^^^
name: string;
age: number;
}

function greet(person: Person) {


return "Hello " + person.name;
}

or a type alias.

type Person = {
// ^^^^^^
name: string;
age: number;
};

function greet(person: Person) {


return "Hello " + person.name;
}

In all three examples above, we've written functions that take objects that contain the property name
(which must be a string ) and age (which must be a number ).

Property Modifiers
Each property in an object type can specify a couple of things: the type, whether the property is optional,
and whether the property can be written to.

Optional Properties
Much of the time, we'll find ourselves dealing with objects that might have a property set. In those cases,
we can mark those properties as optional by adding a question mark ( ? ) to the end of their names.

interface Shape {}
declare function getShape(): Shape;

// ---cut---
interface PaintOptions {
shape: Shape;
xPos?: number;

226
Object Types

// ^
yPos?: number;
// ^
}

function paintShape(opts: PaintOptions) {


// ...
}

const shape = getShape();


paintShape({ shape });
paintShape({ shape, xPos: 100 });
paintShape({ shape, yPos: 100 });
paintShape({ shape, xPos: 100, yPos: 100 });

In this example, both xPos and yPos are considered optional. We can choose to provide either of them,
so every call above to paintShape is valid. All optionality really says is that if the property is set, it better
have a specific type.

We can also read from those properties - but when we do under strictNullChecks , TypeScript will tell us
they're potentially undefined .

interface Shape {}
declare function getShape(): Shape;

interface PaintOptions {
shape: Shape;
xPos?: number;
yPos?: number;
}

// ---cut---
function paintShape(opts: PaintOptions) {
let xPos = opts.xPos;
// ^?
let yPos = opts.yPos;
// ^?
// ...
}

In JavaScript, even if the property has never been set, we can still access it - it's just going to give us the
value undefined . We can just handle undefined specially.

interface Shape {}
declare function getShape(): Shape;

interface PaintOptions {
shape: Shape;
xPos?: number;
yPos?: number;
}

// ---cut---
function paintShape(opts: PaintOptions) {
let xPos = opts.xPos === undefined ? 0 : opts.xPos;
// ^?
let yPos = opts.yPos === undefined ? 0 : opts.yPos;

227
typescript

// ^?
// ...
}

Note that this pattern of setting defaults for unspecified values is so common that JavaScript has syntax to
support it.

interface Shape {}
declare function getShape(): Shape;

interface PaintOptions {
shape: Shape;
xPos?: number;
yPos?: number;
}

// ---cut---
function paintShape({ shape, xPos = 0, yPos = 0 }: PaintOptions) {
console.log("x coordinate at", xPos);
// ^?
console.log("y coordinate at", yPos);
// ^?
// ...
}

Here we used a destructuring pattern for paintShape 's parameter, and provided default values for xPos
and yPos . Now xPos and yPos are both definitely present within the body of paintShape , but optional
for any callers to paintShape .

Note that there is currently no way to place type annotations within destructuring patterns. This is be-
cause the following syntax already means something different in JavaScript.

// @noImplicitAny: false
// @errors: 2552 2304
interface Shape {}
declare function render(x: unknown);
// ---cut---
function draw({ shape: Shape, xPos: number = 100 /*...*/ }) {
render(shape);
render(xPos);
}

In an object destructuring pattern, shape: Shape means "grab the property shape and redefine it locally
as a variable named Shape . Likewise xPos: number creates a variable named number whose value is
based on the parameter's xPos .

Using mapping modifiers, you can remove optional attributes.

readonly Properties
Properties can also be marked as readonly for TypeScript. While it won't change any behavior at runtime,
a property marked as readonly can't be written to during type-checking.

228
Object Types

// @errors: 2540
interface SomeType {
readonly prop: string;
}

function doSomething(obj: SomeType) {


// We can read from 'obj.prop'.
console.log(`prop has the value '${obj.prop}'.`);

// But we can't re-assign it.


obj.prop = "hello";
}

Using the readonly modifier doesn't necessarily imply that a value is totally immutable - or in other
words, that its internal contents can't be changed. It just means the property itself can't be re-written to.

// @errors: 2540
interface Home {
readonly resident: { name: string; age: number };
}

function visitForBirthday(home: Home) {


// We can read and update properties from 'home.resident'.
console.log(`Happy birthday ${home.resident.name}!`);
home.resident.age++;
}

function evict(home: Home) {


// But we can't write to the 'resident' property itself on a 'Home'.
home.resident = {
name: "Victor the Evictor",
age: 42,
};
}

It's important to manage expectations of what readonly implies. It's useful to signal intent during devel-
opment time for TypeScript on how an object should be used. TypeScript doesn't factor in whether proper-
ties on two types are readonly when checking whether those types are compatible, so readonly proper-
ties can also change via aliasing.

interface Person {
name: string;
age: number;
}

interface ReadonlyPerson {
readonly name: string;
readonly age: number;
}

let writablePerson: Person = {


name: "Person McPersonface",
age: 42,
};

// works
let readonlyPerson: ReadonlyPerson = writablePerson;

229
typescript

console.log(readonlyPerson.age); // prints '42'


writablePerson.age++;
console.log(readonlyPerson.age); // prints '43'

Using mapping modifiers, you can remove readonly attributes.

Index Signatures
Sometimes you don't know all the names of a type's properties ahead of time, but you do know the shape
of the values.

In those cases you can use an index signature to describe the types of possible values, for example:

declare function getStringArray(): StringArray;


// ---cut---
interface StringArray {
[index: number]: string;
}

const myArray: StringArray = getStringArray();


const secondItem = myArray[1];
// ^?

Above, we have a StringArray interface which has an index signature. This index signature states that
when a StringArray is indexed with a number , it will return a string .

Only some types are allowed for index signature properties: string , number , symbol , template string
patterns, and union types consisting only of these.

It is possible to support both types of indexers...

230
Object Types

Details

While string index signatures are a powerful way to describe the "dictionary" pattern, they also enforce that
all properties match their return type. This is because a string index declares that obj.property is also
available as obj["property"] . In the following example, name 's type does not match the string index's
type, and the type checker gives an error:

// @errors: 2411
// @errors: 2411
interface NumberDictionary {
[index: string]: number;

length: number; // ok
name: string;
}

However, properties of different types are acceptable if the index signature is a union of the property types:

interface NumberOrStringDictionary {
[index: string]: number | string;
length: number; // ok, length is a number
name: string; // ok, name is a string
}

Finally, you can make index signatures readonly in order to prevent assignment to their indices:

declare function getReadOnlyStringArray(): ReadonlyStringArray;


// ---cut---
// @errors: 2542
interface ReadonlyStringArray {
readonly [index: number]: string;
}

let myArray: ReadonlyStringArray = getReadOnlyStringArray();


myArray[2] = "Mallory";

You can't set myArray[2] because the index signature is readonly .

Extending Types
It's pretty common to have types that might be more specific versions of other types. For example, we
might have a BasicAddress type that describes the fields necessary for sending letters and packages in
the U.S.

interface BasicAddress {
name?: string;
street: string;
city: string;
country: string;
postalCode: string;
}

In some situations that's enough, but addresses often have a unit number associated with them if the build-
ing at an address has multiple units. We can then describe an AddressWithUnit .

231
typescript

interface AddressWithUnit {
name?: string;
unit: string;
//^^^^^^^^^^^^^
street: string;
city: string;
country: string;
postalCode: string;
}

This does the job, but the downside here is that we had to repeat all the other fields from BasicAddress
when our changes were purely additive. Instead, we can extend the original BasicAddress type and just
add the new fields that are unique to AddressWithUnit .

interface BasicAddress {
name?: string;
street: string;
city: string;
country: string;
postalCode: string;
}

interface AddressWithUnit extends BasicAddress {


unit: string;
}

The extends keyword on an interface allows us to effectively copy members from other named types,
and add whatever new members we want. This can be useful for cutting down the amount of type declara-
tion boilerplate we have to write, and for signaling intent that several different declarations of the same
property might be related. For example, AddressWithUnit didn't need to repeat the street property, and
because street originates from BasicAddress , a reader will know that those two types are related in
some way.

interface s can also extend from multiple types.

interface Colorful {
color: string;
}

interface Circle {
radius: number;
}

interface ColorfulCircle extends Colorful, Circle {}

const cc: ColorfulCircle = {


color: "red",
radius: 42,
};

Intersection Types
interface s allowed us to build up new types from other types by extending them. TypeScript provides an-
other construct called intersection types that is mainly used to combine existing object types.

232
Object Types

An intersection type is defined using the & operator.

interface Colorful {
color: string;
}
interface Circle {
radius: number;
}

type ColorfulCircle = Colorful & Circle;

Here, we've intersected Colorful and Circle to produce a new type that has all the members of
Colorful and Circle .

// @errors: 2345
interface Colorful {
color: string;
}
interface Circle {
radius: number;
}
// ---cut---
function draw(circle: Colorful & Circle) {
console.log(`Color was ${circle.color}`);
console.log(`Radius was ${circle.radius}`);
}

// okay
draw({ color: "blue", radius: 42 });

// oops
draw({ color: "red", raidus: 42 });

Interfaces vs. Intersections


We just looked at two ways to combine types which are similar, but are actually subtly different. With inter-
faces, we could use an extends clause to extend from other types, and we were able to do something sim-
ilar with intersections and name the result with a type alias. The principle difference between the two is how
conflicts are handled, and that difference is typically one of the main reasons why you'd pick one over the
other between an interface and a type alias of an intersection type.

Generic Object Types


Let's imagine a Box type that can contain any value - string s, number s, Giraffe s, whatever.

interface Box {
contents: any;
}

Right now, the contents property is typed as any , which works, but can lead to accidents down the line.

We could instead use unknown , but that would mean that in cases where we already know the type of
contents , we'd need to do precautionary checks, or use error-prone type assertions.

233
typescript

interface Box {
contents: unknown;
}

let x: Box = {
contents: "hello world",
};

// we could check 'x.contents'


if (typeof x.contents === "string") {
console.log(x.contents.toLowerCase());
}

// or we could use a type assertion


console.log((x.contents as string).toLowerCase());

One type safe approach would be to instead scaffold out different Box types for every type of contents .

// @errors: 2322
interface NumberBox {
contents: number;
}

interface StringBox {
contents: string;
}

interface BooleanBox {
contents: boolean;
}

But that means we'll have to create different functions, or overloads of functions, to operate on these types.

interface NumberBox {
contents: number;
}

interface StringBox {
contents: string;
}

interface BooleanBox {
contents: boolean;
}
// ---cut---
function setContents(box: StringBox, newContents: string): void;
function setContents(box: NumberBox, newContents: number): void;
function setContents(box: BooleanBox, newContents: boolean): void;
function setContents(box: { contents: any }, newContents: any) {
box.contents = newContents;
}

That's a lot of boilerplate. Moreover, we might later need to introduce new types and overloads. This is frus-
trating, since our box types and overloads are all effectively the same.

Instead, we can make a generic Box type which declares a type parameter.

234
Object Types

interface Box<Type> {
contents: Type;
}

You might read this as “A Box of Type is something whose contents have type Type ”. Later on, when
we refer to Box , we have to give a type argument in place of Type .

interface Box<Type> {
contents: Type;
}
// ---cut---
let box: Box<string>;

Think of Box as a template for a real type, where Type is a placeholder that will get replaced with some
other type. When TypeScript sees Box<string> , it will replace every instance of Type in Box<Type> with
string , and end up working with something like { contents: string } . In other words, Box<string>
and our earlier StringBox work identically.

interface Box<Type> {
contents: Type;
}
interface StringBox {
contents: string;
}

let boxA: Box<string> = { contents: "hello" };


boxA.contents;
// ^?

let boxB: StringBox = { contents: "world" };


boxB.contents;
// ^?

Box is reusable in that Type can be substituted with anything. That means that when we need a box for a
new type, we don't need to declare a new Box type at all (though we certainly could if we wanted to).

interface Box<Type> {
contents: Type;
}

interface Apple {
// ....
}

// Same as '{ contents: Apple }'.


type AppleBox = Box<Apple>;

This also means that we can avoid overloads entirely by instead using generic functions.

interface Box<Type> {
contents: Type;
}

// ---cut---

235
typescript

function setContents<Type>(box: Box<Type>, newContents: Type) {


box.contents = newContents;
}

It is worth noting that type aliases can also be generic. We could have defined our new Box<Type> inter-
face, which was:

interface Box<Type> {
contents: Type;
}

by using a type alias instead:

type Box<Type> = {
contents: Type;
};

Since type aliases, unlike interfaces, can describe more than just object types, we can also use them to
write other kinds of generic helper types.

// @errors: 2575
type OrNull<Type> = Type | null;

type OneOrMany<Type> = Type | Type[];

type OneOrManyOrNull<Type> = OrNull<OneOrMany<Type>>;


// ^?

type OneOrManyOrNullStrings = OneOrManyOrNull<string>;


// ^?

We'll circle back to type aliases in just a little bit.

The Array Type


Generic object types are often some sort of container type that work independently of the type of elements
they contain. It's ideal for data structures to work this way so that they're re-usable across different data
types.

It turns out we've been working with a type just like that throughout this handbook: the Array type.
Whenever we write out types like number[] or string[] , that's really just a shorthand for
Array<number> and Array<string> .

function doSomething(value: Array<string>) {


// ...
}

let myArray: string[] = ["hello", "world"];

// either of these work!


doSomething(myArray);
doSomething(new Array("hello", "world"));

Much like the Box type above, Array itself is a generic type.

236
Object Types

// @noLib: true
interface Number {}
interface String {}
interface Boolean {}
interface Symbol {}
// ---cut---
interface Array<Type> {
/**
* Gets or sets the length of the array.
*/
length: number;

/**
* Removes the last element from an array and returns it.
*/
pop(): Type | undefined;

/**
* Appends new elements to an array, and returns the new length of the array.
*/
push(...items: Type[]): number;

// ...
}

Modern JavaScript also provides other data structures which are generic, like Map<K, V> , Set<T> , and
Promise<T> . All this really means is that because of how Map , Set , and Promise behave, they can work
with any sets of types.

The ReadonlyArray Type


The ReadonlyArray is a special type that describes arrays that shouldn't be changed.

// @errors: 2339
function doStuff(values: ReadonlyArray<string>) {
// We can read from 'values'...
const copy = values.slice();
console.log(`The first value is ${values[0]}`);

// ...but we can't mutate 'values'.


values.push("hello!");
}

Much like the readonly modifier for properties, it's mainly a tool we can use for intent. When we see a
function that returns ReadonlyArray s, it tells us we're not meant to change the contents at all, and when
we see a function that consumes ReadonlyArray s, it tells us that we can pass any array into that function
without worrying that it will change its contents.

Unlike Array , there isn't a ReadonlyArray constructor that we can use.

// @errors: 2693
new ReadonlyArray("red", "green", "blue");

Instead, we can assign regular Array s to ReadonlyArray s.

const roArray: ReadonlyArray<string> = ["red", "green", "blue"];

237
typescript

Just as TypeScript provides a shorthand syntax for Array<Type> with Type[] , it also provides a shorthand
syntax for ReadonlyArray<Type> with readonly Type[] .

// @errors: 2339
function doStuff(values: readonly string[]) {
// ^^^^^^^^^^^^^^^^^
// We can read from 'values'...
const copy = values.slice();
console.log(`The first value is ${values[0]}`);

// ...but we can't mutate 'values'.


values.push("hello!");
}

One last thing to note is that unlike the readonly property modifier, assignability isn't bidirectional be-
tween regular Array s and ReadonlyArray s.

// @errors: 4104
let x: readonly string[] = [];
let y: string[] = [];

x = y;
y = x;

Tuple Types
A tuple type is another sort of Array type that knows exactly how many elements it contains, and exactly
which types it contains at specific positions.

type StringNumberPair = [string, number];


// ^^^^^^^^^^^^^^^^

Here, StringNumberPair is a tuple type of string and number . Like ReadonlyArray , it has no repre-
sentation at runtime, but is significant to TypeScript. To the type system, StringNumberPair describes ar-
rays whose 0 index contains a string and whose 1 index contains a number .

function doSomething(pair: [string, number]) {


const a = pair[0];
// ^?
const b = pair[1];
// ^?
// ...
}

doSomething(["hello", 42]);

If we try to index past the number of elements, we'll get an error.

// @errors: 2493
function doSomething(pair: [string, number]) {
// ...

const c = pair[2];
}

238
Object Types

We can also destructure tuples using JavaScript's array destructuring.

function doSomething(stringHash: [string, number]) {


const [inputString, hash] = stringHash;

console.log(inputString);
// ^?

console.log(hash);
// ^?
}

Tuple types are useful in heavily convention-based APIs, where each element's meaning is "obvious".
This gives us flexibility in whatever we want to name our variables when we destructure them. In the
above example, we were able to name elements 0 and 1 to whatever we wanted.

However, since not every user holds the same view of what's obvious, it may be worth reconsidering
whether using objects with descriptive property names may be better for your API.

Other than those length checks, simple tuple types like these are equivalent to types which are versions of
Array s that declare properties for specific indexes, and that declare length with a numeric literal type.

interface StringNumberPair {
// specialized properties
length: 2;
0: string;
1: number;

// Other 'Array<string | number>' members...


slice(start?: number, end?: number): Array<string | number>;
}

Another thing you may be interested in is that tuples can have optional properties by writing out a question
mark ( ? after an element's type). Optional tuple elements can only come at the end, and also affect the
type of length .

type Either2dOr3d = [number, number, number?];

function setCoordinate(coord: Either2dOr3d) {


const [x, y, z] = coord;
// ^?

console.log(`Provided coordinates had ${coord.length} dimensions`);


// ^?
}

Tuples can also have rest elements, which have to be an array/tuple type.

type StringNumberBooleans = [string, number, ...boolean[]];


type StringBooleansNumber = [string, ...boolean[], number];
type BooleansStringNumber = [...boolean[], string, number];

239
typescript

StringNumberBooleans describes a tuple whose first two elements are string and number respec-
tively, but which may have any number of boolean s following.
StringBooleansNumber describes a tuple whose first element is string and then any number of
boolean s and ending with a number .
BooleansStringNumber describes a tuple whose starting elements are any number of boolean s and
ending with a string then a number .

A tuple with a rest element has no set "length" - it only has a set of well-known elements in different
positions.

type StringNumberBooleans = [string, number, ...boolean[]];


// ---cut---
const a: StringNumberBooleans = ["hello", 1];
const b: StringNumberBooleans = ["beautiful", 2, true];
const c: StringNumberBooleans = ["world", 3, true, false, true, false, true];

Why might optional and rest elements be useful? Well, it allows TypeScript to correspond tuples with para-
meter lists. Tuples types can be used in rest parameters and arguments, so that the following:

function readButtonInput(...args: [string, number, ...boolean[]]) {


const [name, version, ...input] = args;
// ...
}

is basically equivalent to:

function readButtonInput(name: string, version: number, ...input: boolean[]) {


// ...
}

This is handy when you want to take a variable number of arguments with a rest parameter, and you need a
minimum number of elements, but you don't want to introduce intermediate variables.

readonly Tuple Types


One final note about tuple types - tuples types have readonly variants, and can be specified by sticking a
readonly modifier in front of them - just like with array shorthand syntax.

function doSomething(pair: readonly [string, number]) {


// ^^^^^^^^^^^^^^^^^^^^^^^^^
// ...
}

As you might expect, writing to any property of a readonly tuple isn't allowed in TypeScript.

// @errors: 2540
function doSomething(pair: readonly [string, number]) {
pair[0] = "hello!";
}

240
Object Types

Tuples tend to be created and left un-modified in most code, so annotating types as readonly tuples when
possible is a good default. This is also important given that array literals with const assertions will be in-
ferred with readonly tuple types.

// @errors: 2345
let point = [3, 4] as const;

function distanceFromOrigin([x, y]: [number, number]) {


return Math.sqrt(x ** 2 + y ** 2);
}

distanceFromOrigin(point);

Here, distanceFromOrigin never modifies its elements, but expects a mutable tuple. Since point 's type
was inferred as readonly [3, 4] , it won't be compatible with [number, number] since that type can't
guarantee point 's elements won't be mutated.

Go to TOC

241
typescript

About this Handbook


Over 20 years after its introduction to the programming community, JavaScript is now one of the most
widespread cross-platform languages ever created. Starting as a small scripting language for adding trivial
interactivity to webpages, JavaScript has grown to be a language of choice for both frontend and backend
applications of every size. While the size, scope, and complexity of programs written in JavaScript has
grown exponentially, the ability of the JavaScript language to express the relationships between different
units of code has not. Combined with JavaScript's rather peculiar runtime semantics, this mismatch be-
tween language and program complexity has made JavaScript development a difficult task to manage at
scale.

The most common kinds of errors that programmers write can be described as type errors: a certain kind of
value was used where a different kind of value was expected. This could be due to simple typos, a failure to
understand the API surface of a library, incorrect assumptions about runtime behavior, or other errors. The
goal of TypeScript is to be a static typechecker for JavaScript programs - in other words, a tool that runs
before your code runs (static) and ensures that the types of the program are correct (typechecked).

If you are coming to TypeScript without a JavaScript background, with the intention of TypeScript being
your first language, we recommend you first start reading the documentation on either the Microsoft Learn
JavaScript tutorial or read JavaScript at the Mozilla Web Docs. If you have experience in other languages,
you should be able to pick up JavaScript syntax quite quickly by reading the handbook.

How is this Handbook Structured


The handbook is split into two sections:

The Handbook

The TypeScript Handbook is intended to be a comprehensive document that explains TypeScript to every-
day programmers. You can read the handbook by going from top to bottom in the left-hand navigation.

You should expect each chapter or page to provide you with a strong understanding of the given con-
cepts. The TypeScript Handbook is not a complete language specification, but it is intended to be a com-
prehensive guide to all of the language's features and behaviors.

A reader who completes the walkthrough should be able to:

Read and understand commonly-used TypeScript syntax and patterns


Explain the effects of important compiler options
Correctly predict type system behavior in most cases

In the interests of clarity and brevity, the main content of the Handbook will not explore every edge case
or minutiae of the features being covered. You can find more details on particular concepts in the refer-
ence articles.

Reference Files

242
The TypeScript Handbook

The reference section below the handbook in the navigation is built to provide a richer understanding of
how a particular part of TypeScript works. You can read it top-to-bottom, but each section aims to pro-
vide a deeper explanation of a single concept - meaning there is no aim for continuity.

Non-Goals
The Handbook is also intended to be a concise document that can be comfortably read in a few hours.
Certain topics won't be covered in order to keep things short.

Specifically, the Handbook does not fully introduce core JavaScript basics like functions, classes, and clo-
sures. Where appropriate, we'll include links to background reading that you can use to read up on those
concepts.

The Handbook also isn't intended to be a replacement for a language specification. In some cases, edge
cases or formal descriptions of behavior will be skipped in favor of high-level, easier-to-understand explana-
tions. Instead, there are separate reference pages that more precisely and formally describe many aspects
of TypeScript's behavior. The reference pages are not intended for readers unfamiliar with TypeScript, so
they may use advanced terminology or reference topics you haven't read about yet.

Finally, the Handbook won't cover how TypeScript interacts with other tools, except where necessary. Topics
like how to configure TypeScript with webpack, rollup, parcel, react, babel, closure, lerna, rush, bazel, pre-
act, vue, angular, svelte, jquery, yarn, or npm are out of scope - you can find these resources elsewhere on
the web.

Get Started
Before getting started with The Basics, we recommend reading one of the following introductory pages.
These introductions are intended to highlight key similarities and differences between TypeScript and your
favored programming language, and clear up common misconceptions specific to those languages.

TypeScript for New Programmers


TypeScript for JavaScript Programmers
TypeScript for OOP Programmers
TypeScript for Functional Programmers

Otherwise, jump to The Basics.

Go to TOC

243
typescript

Throughout the sections you've read so far, we've been demonstrating basic TypeScript concepts using the
built-in functions present in all JavaScript runtimes. However, almost all JavaScript today includes many li-
braries to accomplish common tasks. Having types for the parts of your application that aren't your code
will greatly improve your TypeScript experience. Where do these types come from?

What Do Type Declarations Look Like?


Let's say you write some code like this:

// @errors: 2339
const k = Math.max(5, 6);
const j = Math.mix(7, 8);

How did TypeScript know that max was present but not mix , even though Math 's implementation wasn't
part of your code?

The answer is that there are declaration files describing these built-in objects. A declaration file provides a
way to declare the existence of some types or values without actually providing implementations for those
values.

.d.ts files
TypeScript has two main kinds of files. .ts files are implementation files that contain types and executable
code. These are the files that produce .js outputs, and are where you'd normally write your code.

.d.ts files are declaration files that contain only type information. These files don't produce .js outputs;
they are only used for typechecking. We'll learn more about how to write our own declaration files later.

Built-in Type Definitions


TypeScript includes declaration files for all of the standardized built-in APIs available in JavaScript runtimes.
This includes things like methods and properties of built-in types like string or function , top-level
names like Math and Object , and their associated types. By default, TypeScript also includes types for
things available when running inside the browser, such as window and document ; these are collectively re-
ferred to as the DOM APIs.

TypeScript names these declaration files with the pattern lib.[something].d.ts . If you navigate into a
file with that name, you can know that you're dealing with some built-in part of the platform, not user code.

target setting
The methods, properties, and functions available to you actually vary based on the version of JavaScript
your code is running on. For example, the startsWith method of strings is available only starting with the
version of JavaScript referred as ECMAScript 6.

244
Type Declarations

Being aware of what version of JavaScript your code ultimately runs on is important because you don't want
to use APIs that are from a newer version than the platform you deploy to. This is one function of the tar‐
get compiler setting.

TypeScript helps with this problem by varying which lib files are included by default based on your tar‐
get setting. For example, if target is ES5 , you will see an error if trying to use the startsWith method,
because that method is only available in ES6 or later.

lib setting
The lib setting allows more fine-grained control of which built-in declaration files are considered available
in your program. See the documentation page on lib for more information.

External Definitions
For non-built-in APIs, there are a variety of ways you can get declaration files. How you do this depends on
exactly which library you're getting types for.

Bundled Types
If a library you're using is published as an npm package, it may include type declaration files as part of its
distribution already. You can read the project's documentation to find out, or simply try importing the pack-
age and see if TypeScript is able to automatically resolve the types for you.

If you're a package author considering bundling type definitions with your package, you can read our guide
on bundling type definitions.

DefinitelyTyped / @types
The DefinitelyTyped repository is a centralized repo storing declaration files for thousands of libraries. The
vast majority of commonly-used libraries have declaration files available on DefinitelyTyped.

Definitions on DefinitelyTyped are also automatically published to npm under the @types scope. The name
of the types package is always the same as the name of the underlying package itself. For example, if you
installed the react npm package, you can install its corresponding types by running

npm install --save-dev @types/react

TypeScript automatically finds type definitions under node_modules/@types , so there's no other step need-
ed to get these types available in your program.

Your Own Definitions


In the uncommon event that a library didn't bundle its own types and didn't have a definition on
DefinitelyTyped, you can write a declaration file yourself. See the appendix Writing Declaration Files for a
guide.

245
typescript

If you want to silence warnings about a particular module without writing a declaration file, you can also
quick declare the module as type any by putting an empty declaration for it in a .d.ts file in your project.
For example, if you wanted to use a module named some-untyped-module without having definitions for it,
you would write:

declare module "some-untyped-module";

Go to TOC

246
Conditional Types

At the heart of most useful programs, we have to make decisions based on input. JavaScript programs are
no different, but given the fact that values can be easily introspected, those decisions are also based on the
types of the inputs. Conditional types help describe the relation between the types of inputs and outputs.

interface Animal {
live(): void;
}
interface Dog extends Animal {
woof(): void;
}

type Example1 = Dog extends Animal ? number : string;


// ^?

type Example2 = RegExp extends Animal ? number : string;


// ^?

Conditional types take a form that looks a little like conditional expressions ( condition ? trueExpression
: falseExpression ) in JavaScript:

type SomeType = any;


type OtherType = any;
type TrueType = any;
type FalseType = any;
type Stuff =
// ---cut---
SomeType extends OtherType ? TrueType : FalseType;

When the type on the left of the extends is assignable to the one on the right, then you'll get the type in
the first branch (the "true" branch); otherwise you'll get the type in the latter branch (the "false" branch).

From the examples above, conditional types might not immediately seem useful - we can tell ourselves
whether or not Dog extends Animal and pick number or string ! But the power of conditional types
comes from using them with generics.

For example, let's take the following createLabel function:

interface IdLabel {
id: number /* some fields */;
}
interface NameLabel {
name: string /* other fields */;
}

function createLabel(id: number): IdLabel;


function createLabel(name: string): NameLabel;
function createLabel(nameOrId: string | number): IdLabel | NameLabel;
function createLabel(nameOrId: string | number): IdLabel | NameLabel {
throw "unimplemented";
}

These overloads for createLabel describe a single JavaScript function that makes a choice based on the
types of its inputs. Note a few things:

247
typescript

1. If a library has to make the same sort of choice over and over throughout its API, this becomes
cumbersome.
2. We have to create three overloads: one for each case when we're sure of the type (one for string and
one for number ), and one for the most general case (taking a string | number ). For every new type
createLabel can handle, the number of overloads grows exponentially.

Instead, we can encode that logic in a conditional type:

interface IdLabel {
id: number /* some fields */;
}
interface NameLabel {
name: string /* other fields */;
}
// ---cut---
type NameOrId<T extends number | string> = T extends number
? IdLabel
: NameLabel;

We can then use that conditional type to simplify our overloads down to a single function with no overloads.

interface IdLabel {
id: number /* some fields */;
}
interface NameLabel {
name: string /* other fields */;
}
type NameOrId<T extends number | string> = T extends number
? IdLabel
: NameLabel;
// ---cut---
function createLabel<T extends number | string>(idOrName: T): NameOrId<T> {
throw "unimplemented";
}

let a = createLabel("typescript");
// ^?

let b = createLabel(2.8);
// ^?

let c = createLabel(Math.random() ? "hello" : 42);


// ^?

Conditional Type Constraints


Often, the checks in a conditional type will provide us with some new information. Just like with narrowing
with type guards can give us a more specific type, the true branch of a conditional type will further con-
strain generics by the type we check against.

For example, let's take the following:

// @errors: 2536
type MessageOf<T> = T["message"];

248
Conditional Types

In this example, TypeScript errors because T isn't known to have a property called message . We could
constrain T , and TypeScript would no longer complain:

type MessageOf<T extends { message: unknown }> = T["message"];

interface Email {
message: string;
}

type EmailMessageContents = MessageOf<Email>;


// ^?

However, what if we wanted MessageOf to take any type, and default to something like never if a mes‐
sage property isn't available? We can do this by moving the constraint out and introducing a conditional
type:

type MessageOf<T> = T extends { message: unknown } ? T["message"] : never;

interface Email {
message: string;
}

interface Dog {
bark(): void;
}

type EmailMessageContents = MessageOf<Email>;


// ^?

type DogMessageContents = MessageOf<Dog>;


// ^?

Within the true branch, TypeScript knows that T will have a message property.

As another example, we could also write a type called Flatten that flattens array types to their element
types, but leaves them alone otherwise:

type Flatten<T> = T extends any[] ? T[number] : T;

// Extracts out the element type.


type Str = Flatten<string[]>;
// ^?

// Leaves the type alone.


type Num = Flatten<number>;
// ^?

When Flatten is given an array type, it uses an indexed access with number to fetch out string[] 's
element type. Otherwise, it just returns the type it was given.

Inferring Within Conditional Types


We just found ourselves using conditional types to apply constraints and then extract out types. This ends
up being such a common operation that conditional types make it easier.

249
typescript

Conditional types provide us with a way to infer from types we compare against in the true branch using the
infer keyword. For example, we could have inferred the element type in Flatten instead of fetching it
out "manually" with an indexed access type:

type Flatten<Type> = Type extends Array<infer Item> ? Item : Type;

Here, we used the infer keyword to declaratively introduce a new generic type variable named Item in-
stead of specifying how to retrieve the element type of T within the true branch. This frees us from having
to think about how to dig through and probing apart the structure of the types we're interested in.

We can write some useful helper type aliases using the infer keyword. For example, for simple cases, we
can extract the return type out from function types:

type GetReturnType<Type> = Type extends (...args: never[]) => infer Return


? Return
: never;

type Num = GetReturnType<() => number>;


// ^?

type Str = GetReturnType<(x: string) => string>;


// ^?

type Bools = GetReturnType<(a: boolean, b: boolean) => boolean[]>;


// ^?

When inferring from a type with multiple call signatures (such as the type of an overloaded function), infer-
ences are made from the last signature (which, presumably, is the most permissive catch-all case). It is not
possible to perform overload resolution based on a list of argument types.

declare function stringOrNum(x: string): number;


declare function stringOrNum(x: number): string;
declare function stringOrNum(x: string | number): string | number;

type T1 = ReturnType<typeof stringOrNum>;


// ^?

Distributive Conditional Types


When conditional types act on a generic type, they become distributive when given a union type. For exam-
ple, take the following:

type ToArray<Type> = Type extends any ? Type[] : never;

If we plug a union type into ToArray , then the conditional type will be applied to each member of that
union.

type ToArray<Type> = Type extends any ? Type[] : never;

type StrArrOrNumArr = ToArray<string | number>;


// ^?

250
Conditional Types

What happens here is that StrArrOrNumArr distributes on:

type StrArrOrNumArr =
// ---cut---
string | number;

and maps over each member type of the union, to what is effectively:

type ToArray<Type> = Type extends any ? Type[] : never;


type StrArrOrNumArr =
// ---cut---
ToArray<string> | ToArray<number>;

which leaves us with:

type StrArrOrNumArr =
// ---cut---
string[] | number[];

Typically, distributivity is the desired behavior. To avoid that behavior, you can surround each side of the
extends keyword with square brackets.

type ToArrayNonDist<Type> = [Type] extends [any] ? Type[] : never;

// 'StrArrOrNumArr' is no longer a union.


type StrArrOrNumArr = ToArrayNonDist<string | number>;
// ^?

Go to TOC

251
typescript

A major part of software engineering is building components that not only have well-defined and consistent
APIs, but are also reusable. Components that are capable of working on the data of today as well as the
data of tomorrow will give you the most flexible capabilities for building up large software systems.

In languages like C# and Java, one of the main tools in the toolbox for creating reusable components is
generics, that is, being able to create a component that can work over a variety of types rather than a sin-
gle one. This allows users to consume these components and use their own types.

Hello World of Generics


To start off, let's do the "hello world" of generics: the identity function. The identity function is a function
that will return back whatever is passed in. You can think of this in a similar way to the echo command.

Without generics, we would either have to give the identity function a specific type:

function identity(arg: number): number {


return arg;
}

Or, we could describe the identity function using the any type:

function identity(arg: any): any {


return arg;
}

While using any is certainly generic in that it will cause the function to accept any and all types for the type
of arg , we actually are losing the information about what that type was when the function returns. If we
passed in a number, the only information we have is that any type could be returned.

Instead, we need a way of capturing the type of the argument in such a way that we can also use it to de-
note what is being returned. Here, we will use a type variable, a special kind of variable that works on types
rather than values.

function identity<Type>(arg: Type): Type {


return arg;
}

We've now added a type variable Type to the identity function. This Type allows us to capture the type
the user provides (e.g. number ), so that we can use that information later. Here, we use Type again as
the return type. On inspection, we can now see the same type is used for the argument and the return
type. This allows us to traffic that type information in one side of the function and out the other.

We say that this version of the identity function is generic, as it works over a range of types. Unlike us-
ing any , it's also just as precise (i.e., it doesn't lose any information) as the first identity function that
used numbers for the argument and return type.

Once we've written the generic identity function, we can call it in one of two ways. The first way is to pass
all of the arguments, including the type argument, to the function:

252
Generics

function identity<Type>(arg: Type): Type {


return arg;
}
// ---cut---
let output = identity<string>("myString");
// ^?

Here we explicitly set Type to be string as one of the arguments to the function call, denoted using the
<> around the arguments rather than () .

The second way is also perhaps the most common. Here we use type argument inference -- that is, we
want the compiler to set the value of Type for us automatically based on the type of the argument we pass
in:

function identity<Type>(arg: Type): Type {


return arg;
}
// ---cut---
let output = identity("myString");
// ^?

Notice that we didn't have to explicitly pass the type in the angle brackets ( <> ); the compiler just looked at
the value "myString" , and set Type to its type. While type argument inference can be a helpful tool to
keep code shorter and more readable, you may need to explicitly pass in the type arguments as we did in
the previous example when the compiler fails to infer the type, as may happen in more complex examples.

Working with Generic Type Variables


When you begin to use generics, you'll notice that when you create generic functions like identity , the
compiler will enforce that you use any generically typed parameters in the body of the function correctly.
That is, that you actually treat these parameters as if they could be any and all types.

Let's take our identity function from earlier:

function identity<Type>(arg: Type): Type {


return arg;
}

What if we want to also log the length of the argument arg to the console with each call? We might be
tempted to write this:

// @errors: 2339
function loggingIdentity<Type>(arg: Type): Type {
console.log(arg.length);
return arg;
}

When we do, the compiler will give us an error that we're using the .length member of arg , but nowhere
have we said that arg has this member. Remember, we said earlier that these type variables stand in for
any and all types, so someone using this function could have passed in a number instead, which does not
have a .length member.

253
typescript

Let's say that we've actually intended this function to work on arrays of Type rather than Type directly.
Since we're working with arrays, the .length member should be available. We can describe this just like
we would create arrays of other types:

function loggingIdentity<Type>(arg: Type[]): Type[] {


console.log(arg.length);
return arg;
}

You can read the type of loggingIdentity as "the generic function loggingIdentity takes a type para-
meter Type , and an argument arg which is an array of Type s, and returns an array of Type s." If we
passed in an array of numbers, we'd get an array of numbers back out, as Type would bind to number .
This allows us to use our generic type variable Type as part of the types we're working with, rather than
the whole type, giving us greater flexibility.

We can alternatively write the sample example this way:

function loggingIdentity<Type>(arg: Array<Type>): Array<Type> {


console.log(arg.length); // Array has a .length, so no more error
return arg;
}

You may already be familiar with this style of type from other languages. In the next section, we'll cover
how you can create your own generic types like Array<Type> .

Generic Types
In previous sections, we created generic identity functions that worked over a range of types. In this
section, we'll explore the type of the functions themselves and how to create generic interfaces.

The type of generic functions is just like those of non-generic functions, with the type parameters listed
first, similarly to function declarations:

function identity<Type>(arg: Type): Type {


return arg;
}

let myIdentity: <Type>(arg: Type) => Type = identity;

We could also have used a different name for the generic type parameter in the type, so long as the number
of type variables and how the type variables are used line up.

function identity<Type>(arg: Type): Type {


return arg;
}

let myIdentity: <Input>(arg: Input) => Input = identity;

We can also write the generic type as a call signature of an object literal type:

254
Generics

function identity<Type>(arg: Type): Type {


return arg;
}

let myIdentity: { <Type>(arg: Type): Type } = identity;

Which leads us to writing our first generic interface. Let's take the object literal from the previous example
and move it to an interface:

interface GenericIdentityFn {
<Type>(arg: Type): Type;
}

function identity<Type>(arg: Type): Type {


return arg;
}

let myIdentity: GenericIdentityFn = identity;

In a similar example, we may want to move the generic parameter to be a parameter of the whole
interface. This lets us see what type(s) we're generic over (e.g. Dictionary<string> rather than just
Dictionary ). This makes the type parameter visible to all the other members of the interface.

interface GenericIdentityFn<Type> {
(arg: Type): Type;
}

function identity<Type>(arg: Type): Type {


return arg;
}

let myIdentity: GenericIdentityFn<number> = identity;

Notice that our example has changed to be something slightly different. Instead of describing a generic
function, we now have a non-generic function signature that is a part of a generic type. When we use
GenericIdentityFn , we now will also need to specify the corresponding type argument (here: number ),
effectively locking in what the underlying call signature will use. Understanding when to put the type para-
meter directly on the call signature and when to put it on the interface itself will be helpful in describing
what aspects of a type are generic.

In addition to generic interfaces, we can also create generic classes. Note that it is not possible to create
generic enums and namespaces.

Generic Classes
A generic class has a similar shape to a generic interface. Generic classes have a generic type parameter list
in angle brackets ( <> ) following the name of the class.

// @strict: false
class GenericNumber<NumType> {
zeroValue: NumType;
add: (x: NumType, y: NumType) => NumType;
}

255
typescript

let myGenericNumber = new GenericNumber<number>();


myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) {
return x + y;
};

This is a pretty literal use of the GenericNumber class, but you may have noticed that nothing is restricting
it to only use the number type. We could have instead used string or even more complex objects.

// @strict: false
class GenericNumber<NumType> {
zeroValue: NumType;
add: (x: NumType, y: NumType) => NumType;
}
// ---cut---
let stringNumeric = new GenericNumber<string>();
stringNumeric.zeroValue = "";
stringNumeric.add = function (x, y) {
return x + y;
};

console.log(stringNumeric.add(stringNumeric.zeroValue, "test"));

Just as with interface, putting the type parameter on the class itself lets us make sure all of the properties
of the class are working with the same type.

As we cover in our section on classes, a class has two sides to its type: the static side and the instance side.
Generic classes are only generic over their instance side rather than their static side, so when working with
classes, static members can not use the class's type parameter.

Generic Constraints
If you remember from an earlier example, you may sometimes want to write a generic function that works
on a set of types where you have some knowledge about what capabilities that set of types will have. In our
loggingIdentity example, we wanted to be able to access the .length property of arg , but the com-
piler could not prove that every type had a .length property, so it warns us that we can't make this
assumption.

// @errors: 2339
function loggingIdentity<Type>(arg: Type): Type {
console.log(arg.length);
return arg;
}

Instead of working with any and all types, we'd like to constrain this function to work with any and all types
that also have the .length property. As long as the type has this member, we'll allow it, but it's required
to have at least this member. To do so, we must list our requirement as a constraint on what Type can be.

To do so, we'll create an interface that describes our constraint. Here, we'll create an interface that has a
single .length property and then we'll use this interface and the extends keyword to denote our
constraint:

256
Generics

interface Lengthwise {
length: number;
}

function loggingIdentity<Type extends Lengthwise>(arg: Type): Type {


console.log(arg.length); // Now we know it has a .length property, so no more
error
return arg;
}

Because the generic function is now constrained, it will no longer work over any and all types:

// @errors: 2345
interface Lengthwise {
length: number;
}

function loggingIdentity<Type extends Lengthwise>(arg: Type): Type {


console.log(arg.length);
return arg;
}
// ---cut---
loggingIdentity(3);

Instead, we need to pass in values whose type has all the required properties:

interface Lengthwise {
length: number;
}

function loggingIdentity<Type extends Lengthwise>(arg: Type): Type {


console.log(arg.length);
return arg;
}
// ---cut---
loggingIdentity({ length: 10, value: 3 });

Using Type Parameters in Generic Constraints


You can declare a type parameter that is constrained by another type parameter. For example, here we'd
like to get a property from an object given its name. We'd like to ensure that we're not accidentally grab-
bing a property that does not exist on the obj , so we'll place a constraint between the two types:

// @errors: 2345
function getProperty<Type, Key extends keyof Type>(obj: Type, key: Key) {
return obj[key];
}

let x = { a: 1, b: 2, c: 3, d: 4 };

getProperty(x, "a");
getProperty(x, "m");

257
typescript

Using Class Types in Generics


When creating factories in TypeScript using generics, it is necessary to refer to class types by their con-
structor functions. For example,

function create<Type>(c: { new (): Type }): Type {


return new c();
}

A more advanced example uses the prototype property to infer and constrain relationships between the
constructor function and the instance side of class types.

// @strict: false
class BeeKeeper {
hasMask: boolean = true;
}

class ZooKeeper {
nametag: string = "Mikle";
}

class Animal {
numLegs: number = 4;
}

class Bee extends Animal {


keeper: BeeKeeper = new BeeKeeper();
}

class Lion extends Animal {


keeper: ZooKeeper = new ZooKeeper();
}

function createInstance<A extends Animal>(c: new () => A): A {


return new c();
}

createInstance(Lion).keeper.nametag;
createInstance(Bee).keeper.hasMask;

This pattern is used to power the mixins design pattern.

Go to TOC

258
Indexed Access Types

We can use an indexed access type to look up a specific property on another type:

type Person = { age: number; name: string; alive: boolean };


type Age = Person["age"];
// ^?

The indexing type is itself a type, so we can use unions, keyof , or other types entirely:

type Person = { age: number; name: string; alive: boolean };


// ---cut---
type I1 = Person["age" | "name"];
// ^?

type I2 = Person[keyof Person];


// ^?

type AliveOrName = "alive" | "name";


type I3 = Person[AliveOrName];
// ^?

You'll even see an error if you try to index a property that doesn't exist:

// @errors: 2339
type Person = { age: number; name: string; alive: boolean };
// ---cut---
type I1 = Person["alve"];

Another example of indexing with an arbitrary type is using number to get the type of an array's elements.
We can combine this with typeof to conveniently capture the element type of an array literal:

const MyArray = [
{ name: "Alice", age: 15 },
{ name: "Bob", age: 23 },
{ name: "Eve", age: 38 },
];

type Person = typeof MyArray[number];


// ^?
type Age = typeof MyArray[number]["age"];
// ^?
// Or
type Age2 = Person["age"];
// ^?

You can only use types when indexing, meaning you can't use a const to make a variable reference:

// @errors: 2538 2749


type Person = { age: number; name: string; alive: boolean };
// ---cut---
const key = "age";
type Age = Person[key];

However, you can use a type alias for a similar style of refactor:

259
typescript

type Person = { age: number; name: string; alive: boolean };


// ---cut---
type key = "age";
type Age = Person[key];

Go to TOC

260
Keyof Type Operator

The keyof type operator


The keyof operator takes an object type and produces a string or numeric literal union of its keys. The fol-
lowing type P is the same type as "x" | "y":

type Point = { x: number; y: number };


type P = keyof Point;
// ^?

If the type has a string or number index signature, keyof will return those types instead:

type Arrayish = { [n: number]: unknown };


type A = keyof Arrayish;
// ^?

type Mapish = { [k: string]: boolean };


type M = keyof Mapish;
// ^?

Note that in this example, M is string | number -- this is because JavaScript object keys are always co-
erced to a string, so obj[0] is always the same as obj["0"] .

keyof types become especially useful when combined with mapped types, which we'll learn more about
later.

Go to TOC

261
typescript

When you don't want to repeat yourself, sometimes a type needs to be based on another type.

Mapped types build on the syntax for index signatures, which are used to declare the types of properties
which have not been declared ahead of time:

type Horse = {};


// ---cut---
type OnlyBoolsAndHorses = {
[key: string]: boolean | Horse;
};

const conforms: OnlyBoolsAndHorses = {


del: true,
rodney: false,
};

A mapped type is a generic type which uses a union of PropertyKey s (frequently created via a keyof ) to
iterate through keys to create a type:

type OptionsFlags<Type> = {
[Property in keyof Type]: boolean;
};

In this example, OptionsFlags will take all the properties from the type Type and change their values to
be a boolean.

type OptionsFlags<Type> = {
[Property in keyof Type]: boolean;
};
// ---cut---
type FeatureFlags = {
darkMode: () => void;
newUserProfile: () => void;
};

type FeatureOptions = OptionsFlags<FeatureFlags>;


// ^?

Mapping Modifiers
There are two additional modifiers which can be applied during mapping: readonly and ? which affect
mutability and optionality respectively.

You can remove or add these modifiers by prefixing with - or + . If you don't add a prefix, then + is
assumed.

// Removes 'readonly' attributes from a type's properties


type CreateMutable<Type> = {
-readonly [Property in keyof Type]: Type[Property];
};

type LockedAccount = {
readonly id: string;
readonly name: string;
};

262
Mapped Types

type UnlockedAccount = CreateMutable<LockedAccount>;


// ^?

// Removes 'optional' attributes from a type's properties


type Concrete<Type> = {
[Property in keyof Type]-?: Type[Property];
};

type MaybeUser = {
id: string;
name?: string;
age?: number;
};

type User = Concrete<MaybeUser>;


// ^?

Key Remapping via as


In TypeScript 4.1 and onwards, you can re-map keys in mapped types with an as clause in a mapped type:

type MappedTypeWithNewProperties<Type> = {
[Properties in keyof Type as NewKeyType]: Type[Properties]
}

You can leverage features like template literal types to create new property names from prior ones:

type Getters<Type> = {
[Property in keyof Type as `get${Capitalize<string & Property>}`]: () =>
Type[Property]
};

interface Person {
name: string;
age: number;
location: string;
}

type LazyPerson = Getters<Person>;


// ^?

You can filter out keys by producing never via a conditional type:

// Remove the 'kind' property


type RemoveKindField<Type> = {
[Property in keyof Type as Exclude<Property, "kind">]: Type[Property]
};

interface Circle {
kind: "circle";
radius: number;
}

type KindlessCircle = RemoveKindField<Circle>;


// ^?

You can map over arbitrary unions, not just unions of string | number | symbol , but unions of any type:

263
typescript

type EventConfig<Events extends { kind: string }> = {


[E in Events as E["kind"]]: (event: E) => void;
}

type SquareEvent = { kind: "square", x: number, y: number };


type CircleEvent = { kind: "circle", radius: number };

type Config = EventConfig<SquareEvent | CircleEvent>


// ^?

Further Exploration
Mapped types work well with other features in this type manipulation section, for example here is a mapped
type using a conditional type which returns either a true or false depending on whether an object has
the property pii set to the literal true :

type ExtractPII<Type> = {
[Property in keyof Type]: Type[Property] extends { pii: true } ? true : false;
};

type DBFields = {
id: { format: "incrementing" };
name: { type: string; pii: true };
};

type ObjectsNeedingGDPRDeletion = ExtractPII<DBFields>;


// ^?

Go to TOC

264
Template Literal Types

Template literal types build on string literal types, and have the ability to expand into many strings via
unions.

They have the same syntax as template literal strings in JavaScript, but are used in type positions. When
used with concrete literal types, a template literal produces a new string literal type by concatenating the
contents.

type World = "world";

type Greeting = `hello ${World}`;


// ^?

When a union is used in the interpolated position, the type is the set of every possible string literal that
could be represented by each union member:

type EmailLocaleIDs = "welcome_email" | "email_heading";


type FooterLocaleIDs = "footer_title" | "footer_sendoff";

type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;


// ^?

For each interpolated position in the template literal, the unions are cross multiplied:

type EmailLocaleIDs = "welcome_email" | "email_heading";


type FooterLocaleIDs = "footer_title" | "footer_sendoff";
// ---cut---
type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;
type Lang = "en" | "ja" | "pt";

type LocaleMessageIDs = `${Lang}_${AllLocaleIDs}`;


// ^?

We generally recommend that people use ahead-of-time generation for large string unions, but this is useful
in smaller cases.

String Unions in Types


The power in template literals comes when defining a new string based on information inside a type.

Consider the case where a function ( makeWatchedObject ) adds a new function called on() to a passed
object. In JavaScript, its call might look like: makeWatchedObject(baseObject) . We can imagine the base
object as looking like:

// @noErrors
const passedObject = {
firstName: "Saoirse",
lastName: "Ronan",
age: 26,
};

The on function that will be added to the base object expects two arguments, an eventName (a string )
and a callBack (a function ).

265
typescript

The eventName should be of the form attributeInThePassedObject + "Changed" ; thus, firstName‐


Changed as derived from the attribute firstName in the base object.

The callBack function, when called:

Should be passed a value of the type associated with the name attributeInThePassedObject ; thus,
since firstName is typed as string , the callback for the firstNameChanged event expects a string
to be passed to it at call time. Similarly events associated with age should expect to be called with a
number argument
Should have void return type (for simplicity of demonstration)

The naive function signature of on() might thus be: on(eventName: string, callBack: (newValue:
any) => void) . However, in the preceding description, we identified important type constraints that we'd
like to document in our code. Template Literal types let us bring these constraints into our code.

// @noErrors
declare function makeWatchedObject(obj: any): any;
// ---cut---
const person = makeWatchedObject({
firstName: "Saoirse",
lastName: "Ronan",
age: 26,
});

// makeWatchedObject has added `on` to the anonymous Object

person.on("firstNameChanged", (newValue) => {


console.log(`firstName was changed to ${newValue}!`);
});

Notice that on listens on the event "firstNameChanged" , not just "firstName" . Our naive specification
of on() could be made more robust if we were to ensure that the set of eligible event names was con-
strained by the union of attribute names in the watched object with "Changed" added at the end. While we
are comfortable with doing such a calculation in JavaScript i.e. Object.keys(passedObject).map(x =>
`${x}Changed`) , template literals inside the type system provide a similar approach to string
manipulation:

type PropEventSource<Type> = {
on(eventName: `${string & keyof Type}Changed`, callback: (newValue: any) =>
void): void;
};

/// Create a "watched object" with an 'on' method


/// so that you can watch for changes to properties.
declare function makeWatchedObject<Type>(obj: Type): Type & PropEventSource<Type>;

With this, we can build something that errors when given the wrong property:

// @errors: 2345
type PropEventSource<Type> = {
on(eventName: `${string & keyof Type}Changed`, callback: (newValue: any) =>
void): void;
};

266
Template Literal Types

declare function makeWatchedObject<T>(obj: T): T & PropEventSource<T>;


// ---cut---
const person = makeWatchedObject({
firstName: "Saoirse",
lastName: "Ronan",
age: 26
});

person.on("firstNameChanged", () => {});

// Prevent easy human error (using the key instead of the event name)
person.on("firstName", () => {});

// It's typo-resistant
person.on("frstNameChanged", () => {});

Inference with Template Literals


Notice that we did not benefit from all the information provided in the original passed object. Given change
of a firstName (i.e. a firstNameChanged event), we should expect that the callback will receive an argu-
ment of type string . Similarly, the callback for a change to age should receive a number argument.
We're naively using any to type the callBack 's argument. Again, template literal types make it possible
to ensure an attribute's data type will be the same type as that attribute's callback's first argument.

The key insight that makes this possible is this: we can use a function with a generic such that:

1. The literal used in the first argument is captured as a literal type


2. That literal type can be validated as being in the union of valid attributes in the generic
3. The type of the validated attribute can be looked up in the generic's structure using Indexed Access
4. This typing information can then be applied to ensure the argument to the callback function is of the
same type

type PropEventSource<Type> = {
on<Key extends string & keyof Type>
(eventName: `${Key}Changed`, callback: (newValue: Type[Key]) => void ):
void;
};

declare function makeWatchedObject<Type>(obj: Type): Type & PropEventSource<Type>;

const person = makeWatchedObject({


firstName: "Saoirse",
lastName: "Ronan",
age: 26
});

person.on("firstNameChanged", newName => {


// ^?
console.log(`new name is ${newName.toUpperCase()}`);
});

person.on("ageChanged", newAge => {


// ^?
if (newAge < 0) {
console.warn("warning! negative age");
}
})

267
typescript

Here we made on into a generic method.

When a user calls with the string "firstNameChanged" , TypeScript will try to infer the right type for Key .
To do that, it will match Key against the content prior to "Changed" and infer the string "firstName" .
Once TypeScript figures that out, the on method can fetch the type of firstName on the original object,
which is string in this case. Similarly, when called with "ageChanged" , TypeScript finds the type for the
property age which is number .

Inference can be combined in different ways, often to deconstruct strings, and reconstruct them in different
ways.

Intrinsic String Manipulation Types


To help with string manipulation, TypeScript includes a set of types which can be used in string
manipulation. These types come built-in to the compiler for performance and can't be found in the .d.ts
files included with TypeScript.

Uppercase<StringType>
Converts each character in the string to the uppercase version.

Example

type Greeting = "Hello, world"


type ShoutyGreeting = Uppercase<Greeting>
// ^?

type ASCIICacheKey<Str extends string> = `ID-${Uppercase<Str>}`


type MainID = ASCIICacheKey<"my_app">
// ^?

Lowercase<StringType>
Converts each character in the string to the lowercase equivalent.

Example

type Greeting = "Hello, world"


type QuietGreeting = Lowercase<Greeting>
// ^?

type ASCIICacheKey<Str extends string> = `id-${Lowercase<Str>}`


type MainID = ASCIICacheKey<"MY_APP">
// ^?

Capitalize<StringType>
Converts the first character in the string to an uppercase equivalent.

268
Template Literal Types

Example

type LowercaseGreeting = "hello, world";


type Greeting = Capitalize<LowercaseGreeting>;
// ^?

Uncapitalize<StringType>
Converts the first character in the string to a lowercase equivalent.

Example

type UppercaseGreeting = "HELLO WORLD";


type UncomfortableGreeting = Uncapitalize<UppercaseGreeting>;
// ^?

Technical details on the intrinsic string manipulation types

Go to TOC

269
typescript

The typeof type operator


JavaScript already has a typeof operator you can use in an expression context:

// Prints "string"
console.log(typeof "Hello world");

TypeScript adds a typeof operator you can use in a type context to refer to the type of a variable or
property:

let s = "hello";
let n: typeof s;
// ^?

This isn't very useful for basic types, but combined with other type operators, you can use typeof to con-
veniently express many patterns. For an example, let's start by looking at the predefined type
ReturnType<T> . It takes a function type and produces its return type:

type Predicate = (x: unknown) => boolean;


type K = ReturnType<Predicate>;
// ^?

If we try to use ReturnType on a function name, we see an instructive error:

// @errors: 2749
function f() {
return { x: 10, y: 3 };
}
type P = ReturnType<f>;

Remember that values and types aren't the same thing. To refer to the type that the value f has, we use
typeof :

function f() {
return { x: 10, y: 3 };
}
type P = ReturnType<typeof f>;
// ^?

Limitations
TypeScript intentionally limits the sorts of expressions you can use typeof on.

Specifically, it's only legal to use typeof on identifiers (i.e. variable names) or their properties. This helps
avoid the confusing trap of writing code you think is executing, but isn't:

// @errors: 1005
declare const msgbox: () => boolean;
// type msgbox = any;
// ---cut---
// Meant to use = ReturnType<typeof msgbox>
let shouldContinue: typeof msgbox("Are you sure you want to continue?");

270
Creating Types from Types

TypeScript's type system is very powerful because it allows expressing types in terms of other types.

The simplest form of this idea is generics, we actually have a wide variety of type operators available to
use. It's also possible to express types in terms of values that we already have.

By combining various type operators, we can express complex operations and values in a succinct, main-
tainable way. In this section we'll cover ways to express a new type in terms of an existing type or value.

Generics - Types which take parameters


Keyof Type Operator - Using the keyof operator to create new types
Typeof Type Operator - Using the typeof operator to create new types
Indexed Access Types - Using Type['a'] syntax to access a subset of a type
Conditional Types - Types which act like if statements in the type system
Mapped Types - Creating types by mapping each property in an existing type
Template Literal Types - Mapped types which change properties via template literal strings

Go to TOC

271
typescript

Understanding Errors
Whenever TypeScript finds an error, it tries to explain what went wrong in as much detail as possible.
Because its type system is structural, this often means providing somewhat lengthy descriptions of where it
found a problem.

Terminology
There is some terminology you'll frequently see in error messages that is helpful to understand.

assignable to

TypeScript considers a type assignable to another type if one is an acceptable substitute for the other. In
other words, a Cat is assignable to an Animal because a Cat is an acceptable substitute for an Animal .

As its name implies, this relationship is used to check the validity of an assignment t = s; by examining
the types of t and s . It's also used to check most other places where two types interact. For example,
when calling a function, each argument's type must be assignable to parameter's declared type.

Informally, if you see T is not assignable to S , you can think of that as TypeScript saying " T and S
are not compatible". However, note that this is a directional relationship: S being assignable to T does not
imply that T is assignable to S .

Examples
Let's look at some example error messages and understand what's going on.

Error Elaborations
Each error starts with a leading message, sometimes followed by more sub-messages. You can think of
each sub-message as answering a "why?" question about the message above it. Let's work through some
examples to see how they work in practice.

Here's an example that produces an error message longer than the example itself:

// @errors: 2322
let a: { m: number[] };
let b = { m: [""] };
a = b;

TypeScript found an error when checking the last line. Its logic for issuing an error follows from its logic for
determining if the assignment is OK:

1. Is b 's type assignable to a 's? No. Why?


2. Because the type of the m property is incompatible. Why?
3. Because b 's m property ( string[] ) is not assignable to a 's m property ( number[] ). Why?
4. Because one array's element type ( string ) is not assignable to the other ( number )

272
Understanding Errors

Extra Properties

// @errors: 2322
type A = { m: number };
const a: A = { m: 10, n: "" };

Union Assignments
// @errors: 2322
type Thing = "none" | { name: string };

const a: Thing = { name: 0 };

Go to TOC

273
typescript

With TypeScript 3.7, TypeScript added support for generating .d.ts files from JavaScript using JSDoc syntax.

This set up means you can own the editor experience of TypeScript-powered editors without porting your
project to TypeScript, or having to maintain .d.ts files in your codebase. TypeScript supports most JSDoc
tags, you can find the reference here.

Setting up your Project to emit .d.ts files


To add creation of .d.ts files in your project, you will need to do up-to four steps:

Add TypeScript to your dev dependencies


Add a tsconfig.json to configure TypeScript
Run the TypeScript compiler to generate the corresponding d.ts files for JS files
(optional) Edit your package.json to reference the types

Adding TypeScript
You can learn how to do this in our installation page.

TSConfig
The TSConfig is a jsonc file which configures both your compiler flags, and declare where to find files. In
this case, you will want a file like the following:

{
// Change this to match your project
"include": ["src/**/*"],

"compilerOptions": {
// Tells TypeScript to read JS files, as
// normally they are ignored as source files
"allowJs": true,
// Generate d.ts files
"declaration": true,
// This compiler run should
// only output d.ts files
"emitDeclarationOnly": true,
// Types should go into this directory.
// Removing this would place the .d.ts files
// next to the .js files
"outDir": "dist",
// go to js file when using IDE functions like
// "Go to Definition" in VSCode
"declarationMap": true
}
}

You can learn more about the options in the tsconfig reference. An alternative to using a TSConfig file is the
CLI, this is the same behavior as a CLI command.

npx -p typescript tsc src/**/*.js --declaration --allowJs --emitDeclarationOnly --


outDir types

274
Creating .d.ts Files from .js files

Run the compiler


You can learn how to do this in our installation page. You want to make sure these files are included in your
package if you have the files in your project's .gitignore .

Editing the package.json


TypeScript replicates the node resolution for modules in a package.json , with an additional step for find-
ing .d.ts files. Roughly, the resolution will first check the optional types field, then the "main" field, and
finally will try index.d.ts in the root.

Package.json Location of default .d.ts

No "types" field checks "main", then index.d.ts

"types": "main.d.ts" main.d.ts

"types": "./dist/main.js" ./dist/main.d.ts

If absent, then "main" is used

Package.json Location of default .d.ts

No "main" field index.d.ts

"main":"index.js" index.d.ts

"main":"./dist/index.js" ./dist/index.d.ts

Tips
If you'd like to write tests for your .d.ts files, try tsd.

Go to TOC

275
typescript

The type system in TypeScript has different levels of strictness when working with a codebase:

A type-system based only on inference with JavaScript code


Incremental typing in JavaScript via JSDoc
Using // @ts-check in a JavaScript file
TypeScript code
TypeScript with strict enabled

Each step represents a move towards a safer type-system, but not every project needs that level of
verification.

TypeScript with JavaScript


This is when you use an editor which uses TypeScript to provide tooling like auto-complete, jump to symbol
and refactoring tools like rename. The homepage has a list of editors which have TypeScript plugins.

Providing Type Hints in JS via JSDoc


In a .js file, types can often be inferred. When types can't be inferred, they can be specified using JSDoc
syntax.

JSDoc annotations come before a declaration will be used to set the type of that declaration. For example:

/** @type {number} */


var x;

x = 0; // OK
x = false; // OK?!

You can find the full list of supported JSDoc patterns in JSDoc Supported Types.

@ts-check
The last line of the previous code sample would raise an error in TypeScript, but it doesn't by default in a JS
project. To enable errors in your JavaScript files add: // @ts-check to the first line in your .js files to
have TypeScript raise it as an error.

// @ts-check
// @errors: 2322
/** @type {number} */
var x;

x = 0; // OK
x = false; // Not OK

If you have a lot of JavaScript files you want to add errors to then you can switch to using a jsconfig.j‐
son . You can skip checking some files by adding a // @ts-nocheck comment to files.

276
JS Projects Utilizing TypeScript

TypeScript may offer you errors which you disagree with, in those cases you can ignore errors on specific
lines by adding // @ts-ignore or // @ts-expect-error on the preceding line.

// @ts-check
/** @type {number} */
var x;

x = 0; // OK
// @ts-expect-error
x = false; // Not OK

To learn more about how JavaScript is interpreted by TypeScript read How TS Type Checks JS

Go to TOC

277
typescript

The list below outlines which constructs are currently supported when using JSDoc annotations to provide
type information in JavaScript files.

Note any tags which are not explicitly listed below (such as @async ) are not yet supported.

Types

@type
@param (or @arg or @argument )
@returns (or @return )
@typedef
@callback
@template

Classes

Property Modifiers @public , @private , @protected , @readonly


@override
@extends (or @augments )
@implements
@class (or @constructor )
@this

Documentation

Documentation tags work in both TypeScript and JavaScript.

@deprecated
@see
@link

Other

@enum
@author
Other supported patterns
Unsupported patterns
Unsupported tags

The meaning is usually the same, or a superset, of the meaning of the tag given at jsdoc.app. The code be-
low describes the differences and gives some example usage of each tag.

Note: You can use the playground to explore JSDoc support.

278
JSDoc Reference

Types
@type
You can reference types with the "@type" tag. The type can be:

1. Primitive, like string or number .


2. Declared in a TypeScript declaration, either global or imported.
3. Declared in a JSDoc @typedef tag.

You can use most JSDoc type syntax and any TypeScript syntax, from the most basic like string to the
most advanced, like conditional types.

/**
* @type {string}
*/
var s;

/** @type {Window} */


var win;

/** @type {PromiseLike<string>} */


var promisedString;

// You can specify an HTML Element with DOM properties


/** @type {HTMLElement} */
var myElement = document.querySelector(selector);
element.dataset.myData = "";

@type can specify a union type — for example, something can be either a string or a boolean.

/**
* @type {string | boolean}
*/
var sb;

You can specify array types using a variety of syntaxes:

/** @type {number[]} */


var ns;
/** @type {Array.<number>} */
var jsdoc;
/** @type {Array<number>} */
var nas;

You can also specify object literal types. For example, an object with properties 'a' (string) and 'b' (number)
uses the following syntax:

/** @type {{ a: string, b: number }} */


var var9;

You can specify map-like and array-like objects using string and number index signatures, using either stan-
dard JSDoc syntax or TypeScript syntax.

279
typescript

/**
* A map-like object that maps arbitrary `string` properties to `number`s.
*
* @type {Object.<string, number>}
*/
var stringToNumber;

/** @type {Object.<number, object>} */


var arrayLike;

The preceding two types are equivalent to the TypeScript types { [x: string]: number } and { [x:
number]: any } . The compiler understands both syntaxes.

You can specify function types using either TypeScript or Google Closure syntax:

/** @type {function(string, boolean): number} Closure syntax */


var sbn;
/** @type {(s: string, b: boolean) => number} TypeScript syntax */
var sbn2;

Or you can just use the unspecified Function type:

/** @type {Function} */


var fn7;
/** @type {function} */
var fn6;

Other types from Closure also work:

/**
* @type {*} - can be 'any' type
*/
var star;
/**
* @type {?} - unknown type (same as 'any')
*/
var question;

Casts

TypeScript borrows cast syntax from Google Closure. This lets you cast types to other types by adding a
@type tag before any parenthesized expression.

/**
* @type {number | string}
*/
var numberOrString = Math.random() < 0.5 ? "hello" : 100;
var typeAssertedNumber = /** @type {number} */ (numberOrString);

You can even cast to const just like TypeScript:

let one = /** @type {const} */(1);

280
JSDoc Reference

Import types

You can import declarations from other files using import types. This syntax is TypeScript-specific and dif-
fers from the JSDoc standard:

// @filename: types.d.ts
export type Pet = {
name: string,
};

// @filename: main.js
/**
* @param { import("./types").Pet } p
*/
function walk(p) {
console.log(`Walking ${p.name}...`);
}

import types can be used in type alias declarations:

// @filename: types.d.ts
export type Pet = {
name: string,
};
// @filename: main.js
// ---cut---
/**
* @typedef { import("./types").Pet } Pet
*/

/**
* @type {Pet}
*/
var myPet;
myPet.name;

import types can be used to get the type of a value from a module if you don't know the type, or if it has a
large type that is annoying to type:

// @filename: accounts.d.ts
export const userAccount = {
name: "Name",
address: "An address",
postalCode: "",
country: "",
planet: "",
system: "",
galaxy: "",
universe: "",
};
// @filename: main.js
// ---cut---
/**
* @type {typeof import("./accounts").userAccount }
*/
var x = require("./accounts").userAccount;

281
typescript

@param and @returns


@param uses the same type syntax as @type , but adds a parameter name. The parameter may also be
declared optional by surrounding the name with square brackets:

// Parameters may be declared in a variety of syntactic forms


/**
* @param {string} p1 - A string param.
* @param {string=} p2 - An optional param (Google Closure syntax)
* @param {string} [p3] - Another optional param (JSDoc syntax).
* @param {string} [p4="test"] - An optional param with a default value
* @returns {string} This is the result
*/
function stringsStringStrings(p1, p2, p3, p4) {
// TODO
}

Likewise, for the return type of a function:

/**
* @return {PromiseLike<string>}
*/
function ps() {}

/**
* @returns {{ a: string, b: number }} - May use '@returns' as well as '@return'
*/
function ab() {}

@typedef , @callback , and @param


You can define complex types with @typedef . Similar syntax works with @param .

/**
* @typedef {Object} SpecialType - creates a new type named 'SpecialType'
* @property {string} prop1 - a string property of SpecialType
* @property {number} prop2 - a number property of SpecialType
* @property {number=} prop3 - an optional number property of SpecialType
* @prop {number} [prop4] - an optional number property of SpecialType
* @prop {number} [prop5=42] - an optional number property of SpecialType with
default
*/

/** @type {SpecialType} */


var specialTypeObject;
specialTypeObject.prop3;

You can use either object or Object on the first line.

/**
* @typedef {object} SpecialType1 - creates a new type named 'SpecialType'
* @property {string} prop1 - a string property of SpecialType
* @property {number} prop2 - a number property of SpecialType
* @property {number=} prop3 - an optional number property of SpecialType
*/

282
JSDoc Reference

/** @type {SpecialType1} */


var specialTypeObject1;

@param allows a similar syntax for one-off type specifications. Note that the nested property names must
be prefixed with the name of the parameter:

/**
* @param {Object} options - The shape is the same as SpecialType above
* @param {string} options.prop1
* @param {number} options.prop2
* @param {number=} options.prop3
* @param {number} [options.prop4]
* @param {number} [options.prop5=42]
*/
function special(options) {
return (options.prop4 || 1001) + options.prop5;
}

@callback is similar to @typedef , but it specifies a function type instead of an object type:

/**
* @callback Predicate
* @param {string} data
* @param {number} [index]
* @returns {boolean}
*/

/** @type {Predicate} */


const ok = (s) => !(s.length % 2);

Of course, any of these types can be declared using TypeScript syntax in a single-line @typedef :

/** @typedef {{ prop1: string, prop2: string, prop3?: number }} SpecialType */


/** @typedef {(data: string, index?: number) => boolean} Predicate */

@template
You can declare type parameters with the @template tag. This lets you make functions, classes, or types
that are generic:

/**
* @template T
* @param {T} x - A generic parameter that flows through to the return type
* @returns {T}
*/
function id(x) {
return x;
}

const a = id("string");
const b = id(123);
const c = id({});

Use comma or multiple tags to declare multiple type parameters:

283
typescript

/**
* @template T,U,V
* @template W,X
*/

You can also specify a type constraint before the type parameter name. Only the first type parameter in a
list is constrained:

/**
* @template {string} K - K must be a string or string literal
* @template {{ serious(): string }} Seriousalizable - must have a serious method
* @param {K} key
* @param {Seriousalizable} object
*/
function seriousalize(key, object) {
// ????
}

Finally, you can specify a default for a type parameter:

/** @template [T=object] */


class Cache {
/** @param {T} initial */
constructor(T) {
}
}
let c = new Cache()

Classes
Classes can be declared as ES6 classes.

class C {
/**
* @param {number} data
*/
constructor(data) {
// property types can be inferred
this.name = "foo";

// or set explicitly
/** @type {string | null} */
this.title = null;

// or simply annotated, if they're set elsewhere


/** @type {number} */
this.size;

this.initialize(data); // Should error, initializer expects a string


}
/**
* @param {string} s
*/
initialize = function (s) {
this.size = s.length;
};
}

284
JSDoc Reference

var c = new C(0);

// C should only be called with new, but


// because it is JavaScript, this is allowed and
// considered an 'any'.
var result = C(1);

They can also be declared as constructor functions; use @constructor along with @this for this.

Property Modifiers
@public , @private , and @protected work exactly like public , private , and protected in
TypeScript:

// @errors: 2341
// @ts-check

class Car {
constructor() {
/** @private */
this.identifier = 100;
}

printIdentifier() {
console.log(this.identifier);
}
}

const c = new Car();


console.log(c.identifier);

@public is always implied and can be left off, but means that a property can be reached from
anywhere.
@private means that a property can only be used within the containing class.
@protected means that a property can only be used within the containing class, and all derived sub-
classes, but not on dissimilar instances of the containing class.

@public , @private , and @protected do not work in constructor functions.

@readonly
The @readonly modifier ensures that a property is only ever written to during initialization.

// @errors: 2540
// @ts-check

class Car {
constructor() {
/** @readonly */
this.identifier = 100;
}

printIdentifier() {
console.log(this.identifier);
}
}

285
typescript

const c = new Car();


console.log(c.identifier);

@override
@override works the same way as in TypeScript; use it on methods that override a method from a base
class:

export class C {
m() { }
}
class D extends C {
/** @override */
m() { }
}

Set noImplicitOverride: true in tsconfig to check overrides.

@extends
When JavaScript classes extend a generic base class, there is no JavaScript syntax for passing a type argu-
ment. The @extends tag allows this:

/**
* @template T
* @extends {Set<T>}
*/
class SortableSet extends Set {
// ...
}

Note that @extends only works with classes. Currently, there is no way for a constructor function to extend
a class.

@implements
In the same way, there is no JavaScript syntax for implementing a TypeScript interface. The @implements
tag works just like in TypeScript:

/** @implements {Print} */


class TextBook {
print() {
// TODO
}
}

@constructor
The compiler infers constructor functions based on this-property assignments, but you can make checking
stricter and suggestions better if you add a @constructor tag:

// @checkJs
// @errors: 2345 2348
/**

286
JSDoc Reference

* @constructor
* @param {number} data
*/
function C(data) {
// property types can be inferred
this.name = "foo";

// or set explicitly
/** @type {string | null} */
this.title = null;

// or simply annotated, if they're set elsewhere


/** @type {number} */
this.size;

this.initialize(data);
}
/**
* @param {string} s
*/
C.prototype.initialize = function (s) {
this.size = s.length;
};

var c = new C(0);


c.size;

var result = C(1);

Note: Error messages only show up in JS codebases with a JSConfig and checkJs enabled.

With @constructor , this is checked inside the constructor function C , so you will get suggestions for
the initialize method and an error if you pass it a number. Your editor may also show warnings if you
call C instead of constructing it.

Unfortunately, this means that constructor functions that are also callable cannot use @constructor .

@this
The compiler can usually figure out the type of this when it has some context to work with. When it
doesn't, you can explicitly specify the type of this with @this :

/**
* @this {HTMLElement}
* @param {*} e
*/
function callbackForLater(e) {
this.clientHeight = parseInt(e); // should be fine!
}

Documentation
@deprecated

287
typescript

When a function, method, or property is deprecated you can let users know by marking it with a /** @dep‐
recated */ JSDoc comment. That information is surfaced in completion lists and as a suggestion diagnos-
tic that editors can handle specially. In an editor like VS Code, deprecated values are typically displayed in a
strike-through style like this.

// @noErrors
/** @deprecated */
const apiV1 = {};
const apiV2 = {};

apiV;
// ^|

@see
@see lets you link to other names in your program:

type Box<T> = { t: T }
/** @see Box for implementation details */
type Boxify<T> = { [K in keyof T]: Box<T> };

Some editors will turn Box into a link to make it easy to jump there and back.

@link
@link is like @see , except that it can be used inside other tags:

type Box<T> = { t: T }
/** @returns A {@link Box} containing the parameter. */
function box<U>(u: U): Box<U> {
return { t: u };
}

Other
@enum
The @enum tag allows you to create an object literal whose members are all of a specified type. Unlike most
object literals in JavaScript, it does not allow other members. @enum is intended for compatibility with
Google Closure's @enum tag.

/** @enum {number} */


const JSDocState = {
BeginningOfLine: 0,
SawAsterisk: 1,
SavingComments: 2,
};

JSDocState.SawAsterisk;

Note that @enum is quite different from, and much simpler than, TypeScript's enum . However, unlike
TypeScript's enums, @enum can have any type:

288
JSDoc Reference

/** @enum {function(number): number} */


const MathFuncs = {
add1: (n) => n + 1,
id: (n) => -n,
sub1: (n) => n - 1,
};

MathFuncs.add1;

@author
You can specify the author of an item with @author :

/**
* Welcome to awesome.ts
* @author Ian Awesome <[email protected]>
*/

Remember to surround the email address with angle brackets. Otherwise, @example will be parsed as a
new tag.

Other supported patterns

class Foo {}
// ---cut---
var someObj = {
/**
* @param {string} param1 - JSDocs on property assignments work
*/
x: function (param1) {},
};

/**
* As do jsdocs on variable assignments
* @return {Window}
*/
let someFunc = function () {};

/**
* And class methods
* @param {string} greeting The greeting to use
*/
Foo.prototype.sayHi = (greeting) => console.log("Hi!");

/**
* And arrow function expressions
* @param {number} x - A multiplier
*/
let myArrow = (x) => x * x;

/**
* Which means it works for function components in JSX too
* @param {{a: string, b: number}} props - Some param
*/
var fc = (props) => <div>{props.a.charAt(0)}</div>;

/**
* A parameter can be a class constructor, using Google Closure syntax.
*

289
typescript

* @param {{new(...args: any[]): object}} C - The class to register


*/
function registerClass(C) {}

/**
* @param {...string} p1 - A 'rest' arg (array) of strings. (treated as 'any')
*/
function fn10(p1) {}

/**
* @param {...string} p1 - A 'rest' arg (array) of strings. (treated as 'any')
*/
function fn9(p1) {
return p1.join();
}

Unsupported patterns
Postfix equals on a property type in an object literal type doesn't specify an optional property:

/**
* @type {{ a: string, b: number= }}
*/
var wrong;
/**
* Use postfix question on the property name instead:
* @type {{ a: string, b?: number }}
*/
var right;

Nullable types only have meaning if strictNullChecks is on:

/**
* @type {?number}
* With strictNullChecks: true -- number | null
* With strictNullChecks: false -- number
*/
var nullable;

The TypeScript-native syntax is a union type:

/**
* @type {number | null}
* With strictNullChecks: true -- number | null
* With strictNullChecks: false -- number
*/
var unionNullable;

Non-nullable types have no meaning and are treated just as their original type:

/**
* @type {!number}
* Just has type number
*/
var normal;

290
JSDoc Reference

Unlike JSDoc's type system, TypeScript only allows you to mark types as containing null or not. There is no
explicit non-nullability -- if strictNullChecks is on, then number is not nullable. If it is off, then number is
nullable.

Unsupported tags
TypeScript ignores any unsupported JSDoc tags.

The following tags have open issues to support them:

@const (issue #19672)


@inheritdoc (issue #23215)
@memberof (issue #7237)
@yields (issue #23857)

Go to TOC

291
typescript

Here are some notable differences on how checking works in .js files compared to .ts files.

Properties are inferred from assignments in class


bodies
ES2015 does not have a means for declaring properties on classes. Properties are dynamically assigned,
just like object literals.

In a .js file, the compiler infers properties from property assignments inside the class body. The type of a
property is the type given in the constructor, unless it's not defined there, or the type in the constructor is
undefined or null. In that case, the type is the union of the types of all the right-hand values in these as-
signments. Properties defined in the constructor are always assumed to exist, whereas ones defined just in
methods, getters, or setters are considered optional.

// @checkJs
// @errors: 2322
class C {
constructor() {
this.constructorOnly = 0;
this.constructorUnknown = undefined;
}
method() {
this.constructorOnly = false;
this.constructorUnknown = "plunkbat"; // ok, constructorUnknown is string |
undefined
this.methodOnly = "ok"; // ok, but methodOnly could also be undefined
}
method2() {
this.methodOnly = true; // also, ok, methodOnly's type is string | boolean |
undefined
}
}

If properties are never set in the class body, they are considered unknown. If your class has properties that
are only read from, add and then annotate a declaration in the constructor with JSDoc to specify the type.
You don't even have to give a value if it will be initialized later:

// @checkJs
// @errors: 2322
class C {
constructor() {
/** @type {number | undefined} */
this.prop = undefined;
/** @type {number | undefined} */
this.count;
}
}

let c = new C();


c.prop = 0; // OK
c.count = "string";

292
Type Checking JavaScript Files

Constructor functions are equivalent to classes


Before ES2015, JavaScript used constructor functions instead of classes. The compiler supports this pattern
and understands constructor functions as equivalent to ES2015 classes. The property inference rules de-
scribed above work exactly the same way.

// @checkJs
// @errors: 2683 2322
function C() {
this.constructorOnly = 0;
this.constructorUnknown = undefined;
}
C.prototype.method = function () {
this.constructorOnly = false;
this.constructorUnknown = "plunkbat"; // OK, the type is string | undefined
};

CommonJS modules are supported


In a .js file, TypeScript understands the CommonJS module format. Assignments to exports and mod‐
ule.exports are recognized as export declarations. Similarly, require function calls are recognized as
module imports. For example:

// same as `import module "fs"`


const fs = require("fs");

// same as `export function readFile`


module.exports.readFile = function (f) {
return fs.readFileSync(f);
};

The module support in JavaScript is much more syntactically forgiving than TypeScript's module support.
Most combinations of assignments and declarations are supported.

Classes, functions, and object literals are namespaces


Classes are namespaces in .js files. This can be used to nest classes, for example:

class C {}
C.D = class {};

And, for pre-ES2015 code, it can be used to simulate static methods:

function Outer() {
this.y = 2;
}

Outer.Inner = function () {
this.yy = 2;
};

Outer.Inner();

293
typescript

It can also be used to create simple namespaces:

var ns = {};
ns.C = class {};
ns.func = function () {};

ns;

Other variants are allowed as well:

// IIFE
var ns = (function (n) {
return n || {};
})();
ns.CONST = 1;

// defaulting to global
var assign =
assign ||
function () {
// code goes here
};
assign.extra = 1;

Object literals are open-ended


In a .ts file, an object literal that initializes a variable declaration gives its type to the declaration. No new
members can be added that were not specified in the original literal. This rule is relaxed in a .js file; ob-
ject literals have an open-ended type (an index signature) that allows adding and looking up properties that
were not defined originally. For instance:

var obj = { a: 1 };
obj.b = 2; // Allowed

Object literals behave as if they have an index signature [x:string]: any that allows them to be treated
as open maps instead of closed objects.

Like other special JS checking behaviors, this behavior can be changed by specifying a JSDoc type for the
variable. For example:

// @checkJs
// @errors: 2339
/** @type {{a: number}} */
var obj = { a: 1 };
obj.b = 2;

294
Type Checking JavaScript Files

null, undefined, and empty array initializers are of


type any or any[]
Any variable, parameter or property that is initialized with null or undefined will have type any, even if strict
null checks is turned on. Any variable, parameter or property that is initialized with [] will have type any[],
even if strict null checks is turned on. The only exception is for properties that have multiple initializers as
described above.

function Foo(i = null) {


if (!i) i = 1;
var j = undefined;
j = 2;
this.l = [];
}

var foo = new Foo();


foo.l.push(foo.i);
foo.l.push("end");

Function parameters are optional by default


Since there is no way to specify optionality on parameters in pre-ES2015 JavaScript, all function parameters
in .js file are considered optional. Calls with fewer arguments than the declared number of parameters
are allowed.

It is important to note that it is an error to call a function with too many arguments.

For instance:

// @checkJs
// @strict: false
// @errors: 7006 7006 2554
function bar(a, b) {
console.log(a + " " + b);
}

bar(1); // OK, second argument considered optional


bar(1, 2);
bar(1, 2, 3); // Error, too many arguments

JSDoc annotated functions are excluded from this rule. Use JSDoc optional parameter syntax ( [ ] ) to ex-
press optionality. e.g.:

/**
* @param {string} [somebody] - Somebody's name.
*/
function sayHello(somebody) {
if (!somebody) {
somebody = "John Doe";
}
console.log("Hello " + somebody);
}

sayHello();

295
typescript

Var-args parameter declaration inferred from use of


arguments
A function whose body has a reference to the arguments reference is implicitly considered to have a var-
arg parameter (i.e. (...arg: any[]) => any ). Use JSDoc var-arg syntax to specify the type of the
arguments.

/** @param {...number} args */


function sum(/* numbers */) {
var total = 0;
for (var i = 0; i < arguments.length; i++) {
total += arguments[i];
}
return total;
}

Unspecified type parameters default to any


Since there is no natural syntax for specifying generic type parameters in JavaScript, an unspecified type
parameter defaults to any .

In extends clause
For instance, React.Component is defined to have two type parameters, Props and State . In a .js file,
there is no legal way to specify these in the extends clause. By default the type arguments will be any :

import { Component } from "react";

class MyComponent extends Component {


render() {
this.props.b; // Allowed, since this.props is of type any
}
}

Use JSDoc @augments to specify the types explicitly. for instance:

import { Component } from "react";

/**
* @augments {Component<{a: number}, State>}
*/
class MyComponent extends Component {
render() {
this.props.b; // Error: b does not exist on {a:number}
}
}

In JSDoc references
An unspecified type argument in JSDoc defaults to any:

296
Type Checking JavaScript Files

/** @type{Array} */
var x = [];

x.push(1); // OK
x.push("string"); // OK, x is of type Array<any>

/** @type{Array.<number>} */
var y = [];

y.push(1); // OK
y.push("string"); // Error, string is not assignable to number

In function calls
A call to a generic function uses the arguments to infer the type parameters. Sometimes this process fails to
infer any types, mainly because of lack of inference sources; in these cases, the type parameters will de-
fault to any . For example:

var p = new Promise((resolve, reject) => {


reject();
});

p; // Promise<any>;

To learn all of the features available in JSDoc, see the reference.

Go to TOC

297
typescript

Using the CLI


Running tsc locally will compile the closest project defined by a tsconfig.json , you can compile a set of
TypeScript files by passing in a glob of files you want.

# Run a compile based on a backwards look through the fs for a tsconfig.json


tsc

# Emit JS for just the index.ts with the compiler defaults


tsc index.ts

# Emit JS for any .ts files in the folder src, with the default settings
tsc src/*.ts

# Emit files referenced in with the compiler settings from


tsconfig.production.json
tsc --project tsconfig.production.json

# Emit d.ts files for a js file with showing compiler options which are booleans
tsc index.js --declaration --emitDeclarationOnly

# Emit a single .js file from two files via compiler options which take string
arguments
tsc app.ts util.ts --target esnext --outfile index.js

Compiler Options
If you're looking for more information about the compiler options in a tsconfig, check out the
TSConfig Reference

CLI Commands

Flag Type

--all boolean

Show all compiler options.

--generateTrace string

Generates an event trace and a list of types.

--help boolean

Gives local information for help on the CLI.

298
tsc CLI Options

Flag Type

--init boolean

Initializes a TypeScript project and creates a tsconfig.json file.

--listFilesOnly boolean

Print names of files that are part of the compilation and then stop processing.

--locale string

Set the language of the messaging from TypeScript. This does not affect emit.

--project string

Compile the project given the path to its configuration file, or to a folder with a 'tsconfig.json'.

--showConfig boolean

Print the final configuration instead of building.

--version boolean

Print the compiler's version.

Build Options

Flag Type

--build boolean

Build one or more projects and their dependencies, if out of date

--clean boolean

299
typescript

Flag Type

Delete the outputs of all projects.

--dry boolean

Show what would be built (or deleted, if specified with '--clean')

--force boolean

Build all projects, including those that appear to be up to date.

--verbose boolean

Enable verbose logging.

Watch Options

Flag Type

--
list
excludeDirectories

Remove a list of directories from the watch process.

--excludeFiles list

Remove a list of files from the watch mode's processing.

fixedinterval , priorityinterval , dynamicpriority , or fixed‐


--fallbackPolling
chunksize

Specify what approach the watcher should use if the system runs out of native file watchers.

--
synchronousWatchDi‐ boolean
rectory

300
tsc CLI Options

Flag Type

Synchronously call callbacks and update the state of directory watchers on platforms that don`t support
recursive watching natively.

--watch boolean

Watch input files.

usefsevents , fixedpollinginterval , dynamicprioritypolling ,


--watchDirectory
or fixedchunksizepolling

Specify how directories are watched on systems that lack recursive file-watching functionality.

fixedpollinginterval , prioritypollinginterval , dynamicpri‐


--watchFile oritypolling , fixedchunksizepolling , usefsevents , or usef‐
seventsonparentdirectory

Specify how the TypeScript watch mode works.

Compiler Flags

Flag Type Default

--allowJs boolean false

Allow JavaScript files to be a part of your program. Use the checkJS


option to get errors from these files.

true if module is sys‐


tem , or esModuleIn‐

--allowSyntheticDefaultImports boolean terop and module is not


es6 / es2015 or esnext ,

false otherwise.

Allow 'import x from y' when a module doesn't have a default export.

301
typescript

Flag Type Default

--allowUmdGlobalAccess boolean false

Allow accessing UMD globals from modules.

--allowUnreachableCode boolean

Disable error reporting for unreachable code.

--allowUnusedLabels boolean

Disable error reporting for unused labels.

true if strict ,
--alwaysStrict boolean
false otherwise.

Ensure 'use strict' is always emitted.

--
assumeChangesOnlyAffectDirectDepen‐ boolean false
dencies

Have recompiles in projects that use incremental and watch mode


assume that changes within a file will only affect files directly depending
on it.

--baseUrl string

Specify the base directory to resolve non-relative module names.

--charset string utf8

No longer supported. In early versions, manually set the text encoding


for reading files.

--checkJs boolean false

302
tsc CLI Options

Flag Type Default

Enable error reporting in type-checked JavaScript files.

--composite boolean false

Enable constraints that allow a TypeScript project to be used with


project references.

true if composite ,
--declaration boolean
false otherwise.

Generate .d.ts files from TypeScript and JavaScript files in your project.

--declarationDir string

Specify the output directory for generated declaration files.

--declarationMap boolean false

Create sourcemaps for d.ts files.

--diagnostics boolean false

Output compiler performance information after building.

--disableReferencedProjectLoad boolean false

Reduce the number of projects loaded automatically by TypeScript.

--disableSizeLimit boolean false

Remove the 20mb cap on total source code size for JavaScript files in
the TypeScript language server.

303
typescript

Flag Type Default

--disableSolutionSearching boolean false

Opt a project out of multi-project reference checking when editing.

--
disableSourceOfProjectReferenceRedi‐ boolean false
rect

Disable preferring source files instead of declaration files when referenc-


ing composite projects.

--downlevelIteration boolean false

Emit more compliant, but verbose and less performant JavaScript for
iteration.

--emitBOM boolean false

Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files.

--emitDeclarationOnly boolean false

Only output d.ts files and not JavaScript files.

--emitDecoratorMetadata boolean false

Emit design-type metadata for decorated declarations in source files.

--esModuleInterop boolean false

Emit additional JavaScript to ease support for importing CommonJS


modules. This enables allowSyntheticDefaultImports for type
compatibility.

--exactOptionalPropertyTypes boolean false

304
tsc CLI Options

Flag Type Default

Interpret optional property types as written, rather than adding unde‐


fined .

--experimentalDecorators boolean false

Enable experimental support for TC39 stage 2 draft decorators.

--explainFiles boolean false

Print files read during the compilation including why it was included.

--extendedDiagnostics boolean false

Output more detailed compiler performance information after building.

--forceConsistentCasingInFileNames boolean false

Ensure that casing is correct in imports.

--generateCpuProfile string profile.cpuprofile

Emit a v8 CPU profile of the compiler run for debugging.

--importHelpers boolean false

Allow importing helper functions from tslib once per project, instead of
including them per-file.

remove , pre‐
--importsNotUsedAsValues serve , or er‐ remove
ror

Specify emit/checking behavior for imports that are only used for types.

305
typescript

Flag Type Default

true if composite ,
--incremental boolean
false otherwise.

Save .tsbuildinfo files to allow for incremental compilation of projects.

--inlineSourceMap boolean false

Include sourcemap files inside the emitted JavaScript.

--inlineSources boolean false

Include source code in the sourcemaps inside the emitted JavaScript.

--isolatedModules boolean false

Ensure that each file can be safely transpiled without relying on other
imports.

preserve , re‐
act , react-
--jsx native ,
react-jsx , or
react-jsxdev

Specify what JSX code is generated.

--jsxFactory string React.createElement

Specify the JSX factory function used when targeting React JSX emit,
e.g. 'React.createElement' or 'h'.

--jsxFragmentFactory string React.Fragment

306
tsc CLI Options

Flag Type Default

Specify the JSX Fragment reference used for fragments when targeting
React JSX emit e.g. 'React.Fragment' or 'Fragment'.

--jsxImportSource string react

Specify module specifier used to import the JSX factory functions when
using jsx: react-jsx* .

--keyofStringsOnly boolean false

Make keyof only return strings instead of string, numbers or symbols.


Legacy option.

--lib list

Specify a set of bundled library declaration files that describe the target
runtime environment.

--listEmittedFiles boolean false

Print the names of emitted files after a compilation.

--listFiles boolean false

Print all of the files read during the compilation.

--mapRoot string

Specify the location where debugger should locate map files instead of
generated locations.

--maxNodeModuleJsDepth number 0

307
typescript

Flag Type Default

Specify the maximum folder depth used for checking JavaScript files
from node_modules . Only applicable with allowJs .

none , common‐
js , amd , umd ,
system , CommonJS if target is
es6 / es2015 , ES3 or ES5 ,
--module
es2020 , es‐
2022 , esnext , ES6 / ES2015 otherwise.

node16 , or
nodenext

Specify what module code is generated.

"auto": Treat files with im-


ports, exports, import.meta,
legacy , auto ,
--moduleDetection jsx (with jsx: react-jsx), or
or force
esm format (with module:
node16+) as modules.

Control what method is used to detect the whether a JS file is a module.

Classic if module is
AMD , UMD , System or
classic , ES6 / ES2015 ,
--moduleResolution node , node16 ,
Matches if module is
or nodenext
node12 or nodenext ,

Node otherwise.

Specify how TypeScript looks up a file from a given module specifier.

--moduleSuffixes list

List of file name suffixes to search when resolving a module.

308
tsc CLI Options

Flag Type Default

--newLine crlf or lf Platform specific.

Set the newline character for emitting files.

--noEmit boolean false

Disable emitting files from a compilation.

--noEmitHelpers boolean false

Disable generating custom helper functions like __extends in compiled


output.

--noEmitOnError boolean false

Disable emitting files if any type checking errors are reported.

--noErrorTruncation boolean false

Disable truncating types in error messages.

--noFallthroughCasesInSwitch boolean false

Enable error reporting for fallthrough cases in switch statements.

true if strict ,
--noImplicitAny boolean
false otherwise.

Enable error reporting for expressions and declarations with an implied


any type.

--noImplicitOverride boolean false

309
typescript

Flag Type Default

Ensure overriding members in derived classes are marked with an over-


ride modifier.

--noImplicitReturns boolean false

Enable error reporting for codepaths that do not explicitly return in a


function.

true if strict ,
--noImplicitThis boolean
false otherwise.

Enable error reporting when this is given the type any .

--noImplicitUseStrict boolean false

Disable adding 'use strict' directives in emitted JavaScript files.

--noLib boolean false

Disable including any library files, including the default lib.d.ts.

--
boolean false
noPropertyAccessFromIndexSignature

Enforces using indexed accessors for keys declared using an indexed


type.

--noResolve boolean false

Disallow import s, require s or <reference> s from expanding the


number of files TypeScript should add to a project.

--noStrictGenericChecks boolean false

Disable strict checking of generic signatures in function types.

310
tsc CLI Options

Flag Type Default

--noUncheckedIndexedAccess boolean false

Add undefined to a type when accessed using an index.

--noUnusedLocals boolean false

Enable error reporting when local variables aren't read.

--noUnusedParameters boolean false

Raise an error when a function parameter isn't read.

--out string

Deprecated setting. Use outFile instead.

--outDir string

Specify an output folder for all emitted files.

--outFile string

Specify a file that bundles all outputs into one JavaScript file. If decla‐
ration is true, also designates a file that bundles all .d.ts output.

--paths object

Specify a set of entries that re-map imports to additional lookup


locations.

--plugins list

Specify a list of language service plugins to include.

311
typescript

Flag Type Default

true if isolatedMod‐
ules ,
--preserveConstEnums boolean

false otherwise.

Disable erasing const enum declarations in generated code.

--preserveSymlinks boolean false

Disable resolving symlinks to their realpath. This correlates to the same


flag in node.

--preserveValueImports boolean false

Preserve unused imported values in the JavaScript output that would


otherwise be removed.

--preserveWatchOutput boolean false

Disable wiping the console in watch mode.

--pretty boolean true

Enable color and formatting in TypeScript's output to make compiler er-


rors easier to read.

--reactNamespace string React

Specify the object invoked for createElement . This only applies when
targeting react JSX emit.

--removeComments boolean false

Disable emitting comments.

312
tsc CLI Options

Flag Type Default

--resolveJsonModule boolean false

Enable importing .json files.

Computed from the list of in-


--rootDir string
put files.

Specify the root folder within your source files.

Computed from the list of in-


--rootDirs list
put files.

Allow multiple folders to be treated as one when resolving modules.

--skipDefaultLibCheck boolean false

Skip type checking .d.ts files that are included with TypeScript.

--skipLibCheck boolean false

Skip type checking all .d.ts files.

--sourceMap boolean false

Create source map files for emitted JavaScript files.

--sourceRoot string

Specify the root path for debuggers to find the reference source code.

--strict boolean false

Enable all strict type-checking options.

313
typescript

Flag Type Default

true if strict ,
--strictBindCallApply boolean
false otherwise.

Check that the arguments for bind , call , and apply methods
match the original function.

true if strict ,
--strictFunctionTypes boolean
false otherwise.

When assigning functions, check to ensure parameters and the return


values are subtype-compatible.

true if strict ,
--strictNullChecks boolean
false otherwise.

When type checking, take into account null and undefined .

true if strict ,
--strictPropertyInitialization boolean
false otherwise.

Check for class properties that are declared but not set in the
constructor.

--stripInternal boolean false

Disable emitting declarations that have @internal in their JSDoc


comments.

--suppressExcessPropertyErrors boolean false

Disable reporting of excess property errors during the creation of object


literals.

314
tsc CLI Options

Flag Type Default

--suppressImplicitAnyIndexErrors boolean false

Suppress noImplicitAny errors when indexing objects that lack index


signatures.

es3 , es5 ,
es6 / es2015 ,
es2016 , es‐
2017 , es2018 ,
--target ES3
es2019 , es‐
2020 , es2021 ,
es2022 , or es‐
next

Set the JavaScript language version for emitted JavaScript and include
compatible library declarations.

--traceResolution boolean false

Log paths used during the moduleResolution process.

--tsBuildInfoFile string .tsbuildinfo

Specify the folder for .tsbuildinfo incremental compilation files.

--typeRoots list

Specify multiple folders that act like ./node_modules/@types .

--
list
types

Specify type package names to be included without being referenced in


a source file.

315
typescript

Flag Type Default

true if target is
ES2022 or higher, includ-
--useDefineForClassFields boolean ing ESNext ,

false otherwise.

Emit ECMAScript-standard-compliant class fields.

true if strict ,
--useUnknownInCatchVariables boolean
false otherwise.

Default catch clause variables as unknown instead of any .

Related
Every option is fully explained in the TSConfig Reference.
Learn how to use a tsconfig.json files.
Learn how to work in an MSBuild project.

Go to TOC

316
Compiler Options in MSBuild

Overview
When you have an MSBuild based project which utilizes TypeScript such as an ASP.NET Core project, you
can configure TypeScript in two ways. Either via a tsconfig.json or via the project settings.

Using a tsconfig.json
We recommend using a tsconfig.json for your project when possible. To add one to an existing project,
add a new item to your project which is called a "TypeScript JSON Configuration File" in modern versions of
Visual Studio.

The new tsconfig.json will then be used as the source of truth for TypeScript-specific build information
like files and configuration. You can learn about how TSConfigs works here and there is a comprehensive
reference here.

Using Project Settings


You can also define the configuration for TypeScript inside you project's settings. This is done by editing the
XML in your .csproj to define PropertyGroups which describe how the build can work:

<PropertyGroup>
<TypeScriptNoEmitOnError>true</TypeScriptNoEmitOnError>
<TypeScriptNoImplicitReturns>true</TypeScriptNoImplicitReturns>
</PropertyGroup>

There is a series of mappings for common TypeScript settings, these are settings which map directly to
TypeScript cli options and are used to help you write a more understandable project file. You can use the
TSConfig reference to get more information on what values and defaults are for each mapping.

CLI Mappings

MSBuild
Config TSC Flag
Name

<TypeScriptAllowJS> --allowJs

Allow JavaScript files to be a part of your program. Use the checkJS option to get errors from these
files.

<TypeScriptRemoveComments> --removeComments

Disable emitting comments.

<TypeScriptNoImplicitAny> --noImplicitAny

317
typescript

MSBuild
Config TSC Flag
Name

Enable error reporting for expressions and declarations with an implied any type..

<TypeScriptGeneratesDeclarations> --declaration

Generate .d.ts files from TypeScript and JavaScript files in your project.

<TypeScriptModuleKind> --module

Specify what module code is generated.

<TypeScriptJSXEmit> --jsx

Specify what JSX code is generated.

<TypeScriptOutDir> --outDir

Specify an output folder for all emitted files.

<TypeScriptSourceMap> --sourcemap

Create source map files for emitted JavaScript files.

<TypeScriptTarget> --target

Set the JavaScript language version for emitted JavaScript and include compatible library declarations.

<TypeScriptNoResolve> --noResolve

Disallow import s, require s or <reference> s from expanding the number of files TypeScript should
add to a project.

<TypeScriptMapRoot> --mapRoot

Specify the location where debugger should locate map files instead of generated locations.

<TypeScriptSourceRoot> --sourceRoot

Specify the root path for debuggers to find the reference source code.

<TypeScriptCharset> --charset

318
Compiler Options in MSBuild

MSBuild
Config TSC Flag
Name

No longer supported. In early versions, manually set the text encoding for reading files.

<TypeScriptEmitBOM> --emitBOM

Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files.

<TypeScriptNoLib> --noLib

Disable including any library files, including the default lib.d.ts.

<TypeScriptPreserveConstEnums> --preserveConstEnums

Disable erasing const enum declarations in generated code.

--
<TypeScriptSuppressImplicitAnyIndexErrors> suppressImplicitAnyIndexEr‐
rors

Suppress noImplicitAny errors when indexing objects that lack index signatures.

<TypeScriptNoEmitHelpers> --noEmitHelpers

Disable generating custom helper functions like __extends in compiled output.

<TypeScriptInlineSourceMap> --inlineSourceMap

Include sourcemap files inside the emitted JavaScript.

<TypeScriptInlineSources> --inlineSources

Include source code in the sourcemaps inside the emitted JavaScript.

<TypeScriptNewLine> --newLine

Set the newline character for emitting files.

<TypeScriptIsolatedModules> --isolatedModules

Ensure that each file can be safely transpiled without relying on other imports.

<TypeScriptEmitDecoratorMetadata> --emitDecoratorMetadata

319
typescript

MSBuild
Config TSC Flag
Name

Emit design-type metadata for decorated declarations in source files.

<TypeScriptRootDir> --rootDir

Specify the root folder within your source files.

<TypeScriptExperimentalDecorators> --experimentalDecorators

Enable experimental support for TC39 stage 2 draft decorators.

<TypeScriptModuleResolution> --moduleResolution

Specify how TypeScript looks up a file from a given module specifier.

--
<TypeScriptSuppressExcessPropertyErrors>
suppressExcessPropertyErrors

Disable reporting of excess property errors during the creation of object literals.

<TypeScriptReactNamespace> --reactNamespace

Specify the object invoked for createElement . This only applies when targeting react JSX emit.

<TypeScriptSkipDefaultLibCheck> --skipDefaultLibCheck

Skip type checking .d.ts files that are included with TypeScript.

<TypeScriptAllowUnusedLabels> --allowUnusedLabels

Disable error reporting for unused labels.

<TypeScriptNoImplicitReturns> --noImplicitReturns

Enable error reporting for codepaths that do not explicitly return in a function.

<TypeScriptNoFallthroughCasesInSwitch> --noFallthroughCasesInSwitch

Enable error reporting for fallthrough cases in switch statements.

<TypeScriptAllowUnreachableCode> --allowUnreachableCode

320
Compiler Options in MSBuild

MSBuild
Config TSC Flag
Name

Disable error reporting for unreachable code.

--
<TypeScriptForceConsistentCasingInFile‐
forceConsistentCasingInFile‐
Names>
Names

Ensure that casing is correct in imports.

--
<TypeScriptAllowSyntheticDefaultImports>
allowSyntheticDefaultImports

Allow 'import x from y' when a module doesn't have a default export.

<TypeScriptNoImplicitUseStrict> --noImplicitUseStrict

Disable adding 'use strict' directives in emitted JavaScript files.

<TypeScriptLib> --lib

Specify a set of bundled library declaration files that describe the target runtime environment.

<TypeScriptBaseUrl> --baseUrl

Specify the base directory to resolve non-relative module names.

<TypeScriptDeclarationDir> --declarationDir

Specify the output directory for generated declaration files.

<TypeScriptNoImplicitThis> --noImplicitThis

Enable error reporting when this is given the type any .

<TypeScriptSkipLibCheck> --skipLibCheck

Skip type checking all .d.ts files.

<TypeScriptStrictNullChecks> --strictNullChecks

When type checking, take into account null and undefined .

321
typescript

MSBuild
Config TSC Flag
Name

<TypeScriptNoUnusedLocals> --noUnusedLocals

Enable error reporting when a local variables aren't read.

<TypeScriptNoUnusedParameters> --noUnusedParameters

Raise an error when a function parameter isn't read

<TypeScriptAlwaysStrict> --alwaysStrict

Ensure 'use strict' is always emitted.

<TypeScriptImportHelpers> --importHelpers

Allow importing helper functions from tslib once per project, instead of including them per-file.

<TypeScriptJSXFactory> --jsxFactory

Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'

<TypeScriptStripInternal> --stripInternal

Disable emitting declarations that have @internal in their JSDoc comments.

<TypeScriptCheckJs> --checkJs

Enable error reporting in type-checked JavaScript files.

<TypeScriptDownlevelIteration> --downlevelIteration

Emit more compliant, but verbose and less performant JavaScript for iteration.

<TypeScriptStrict> --strict

Enable all strict type checking options.

<TypeScriptNoStrictGenericChecks> --noStrictGenericChecks

Disable strict checking of generic signatures in function types.

<TypeScriptPreserveSymlinks> --preserveSymlinks

322
Compiler Options in MSBuild

MSBuild
Config TSC Flag
Name

Disable resolving symlinks to their realpath. This correlates to the same flag in node.

<TypeScriptStrictFunctionTypes> --strictFunctionTypes

When assigning functions, check to ensure parameters and the return values are subtype-compatible.

--
<TypeScriptStrictPropertyInitialization>
strictPropertyInitialization

Check for class properties that are declared but not set in the constructor.

<TypeScriptESModuleInterop> --esModuleInterop

Emit additional JavaScript to ease support for importing CommonJS modules. This enables allowSyn‐
theticDefaultImports for type compatibility.

<TypeScriptEmitDeclarationOnly> --emitDeclarationOnly

Only output d.ts files and not JavaScript files.

<TypeScriptKeyofStringsOnly> --keyofStringsOnly

Make keyof only return strings instead of string, numbers or symbols. Legacy option.

<TypeScriptUseDefineForClassFields> --useDefineForClassFields

Emit ECMAScript-standard-compliant class fields.

<TypeScriptDeclarationMap> --declarationMap

Create sourcemaps for d.ts files.

<TypeScriptResolveJsonModule> --resolveJsonModule

Enable importing .json files

<TypeScriptStrictBindCallApply> --strictBindCallApply

Check that the arguments for bind , call , and apply methods match the original function.

<TypeScriptNoEmitOnError> --noEmitOnError

323
typescript

MSBuild
Config TSC Flag
Name

Disable emitting files if any type checking errors are reported.

Additional Flags
Because the MSBuild system passes arguments directly to the TypeScript CLI, you can use the option
TypeScriptAdditionalFlags to provide specific flags which don't have a mapping above.

For example, this would turn on noPropertyAccessFromIndexSignature :

<TypeScriptAdditionalFlags> $(TypeScriptAdditionalFlags) --
noPropertyAccessFromIndexSignature</TypeScriptAdditionalFlags>

Debug and Release Builds


You can use PropertyGroup conditions to define different sets of configurations. For example, a common
task is stripping comments and sourcemaps in production. In this example, we define a debug and release
property group which have different TypeScript configurations:

<PropertyGroup Condition="'$(Configuration)' == 'Debug'">


<TypeScriptRemoveComments>false</TypeScriptRemoveComments>
<TypeScriptSourceMap>true</TypeScriptSourceMap>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)' == 'Release'">


<TypeScriptRemoveComments>true</TypeScriptRemoveComments>
<TypeScriptSourceMap>false</TypeScriptSourceMap>
</PropertyGroup>

<Import

Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\

Condition="Exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\T
/>

ToolsVersion
The value of <TypeScriptToolsVersion>1.7</TypeScriptToolsVersion> property in the project file
identifies the compiler version to use to build (1.7 in this example). This allows a project to build against the
same versions of the compiler on different machines.

If TypeScriptToolsVersion is not specified, the latest compiler version installed on the machine will be
used to build.

Users using newer versions of TS, will see a prompt to upgrade their project on first load.

324
Compiler Options in MSBuild

TypeScriptCompileBlocked
If you are using a different build tool to build your project (e.g. gulp, grunt , etc.) and VS for the develop-
ment and debugging experience, set <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
in your project. This should give you all the editing support, but not the build when you hit F5.

TypeScriptEnableIncrementalMSBuild (TypeScript 4.2 Beta and later)


By default, MSBuild will attempt to only run the TypeScript compiler when the project's source files have
been updated since the last compilation. However, if this behavior is causing issues, such as when
TypeScript's incremental option is enabled, set <TypeScriptEnableIncrementalMSBuild>false</Type‐
ScriptEnableIncrementalMSBuild> to ensure the TypeScript compiler is invoked with every run of
MSBuild.

Go to TOC

325
typescript

Compiler supports configuring how to watch files and directories using compiler flags in TypeScript 3.8+,
and environment variables before that.

Background
The --watch implementation of the compiler relies on using fs.watch and fs.watchFile which are pro-
vided by node, both of these methods have pros and cons.

fs.watch uses file system events to notify the changes in the file/directory. But this is OS dependent and
the notification is not completely reliable and does not work as expected on many OS. Also there could be
limit on number of watches that can be created, e.g. linux and we could exhaust it pretty quickly with pro-
grams that include large number of files. But because this uses file system events, there is not much CPU
cycle involved. Compiler typically uses fs.watch to watch directories (e.g. source directories included by
config file, directories in which module resolution failed etc) These can handle the missing precision in noti-
fying about the changes. But recursive watching is supported on only Windows and OSX. That means we
need something to replace the recursive nature on other OS.

fs.watchFile uses polling and thus involves CPU cycles. However, fs.watchFile is the most reliable
mechanism to get the update on the status of file/directory. The compiler typically uses fs.watchFile to
watch source files, config files and missing files (missing file references). This means the CPU usage when
using fs.watchFile depends on number of files in the program.

Configuring file watching using a tsconfig.json


{
// Some typical compiler options
"compilerOptions": {
"target": "es2020",
"moduleResolution": "node"
// ...
},

// NEW: Options for file/directory watching


"watchOptions": {
// Use native file system events for files and directories
"watchFile": "useFsEvents",
"watchDirectory": "useFsEvents",

// Poll files for updates more frequently


// when they're updated a lot.
"fallbackPolling": "dynamicPriority",

// Don't coalesce watch notification


"synchronousWatchDirectory": true,

// Finally, two additional settings for reducing the amount of possible


// files to track work from these directories
"excludeDirectories": ["**/node_modules", "_build"],
"excludeFiles": ["build/fileWhichChangesOften.ts"]
}
}

326
Configuring Watch

You can read more about this in the release notes.

Configuring file watching using environment variable


TSC_WATCHFILE
Option Description

Use fs.watchFile but use different polling intervals for


PriorityPollingInterval
source files, config files and missing files

Use a dynamic queue where in the frequently modified


DynamicPriorityPolling files will be polled at shorter interval and the files un-
changed will be polled less frequently

Use fs.watch which uses file system events (but might


not be accurate on different OS) to get the notifications
for the file changes/creation/deletion. Note that few OS
UseFsEvents
e.g. linux has limit on number of watches and failing to
create watcher using fs.watch will result it in creating
using fs.watchFile

This option is similar to UseFsEvents except on failing


to create watch using fs.watch , the fallback watching
UseFsEventsWithFallbackDynamicPolling
happens through dynamic polling queues (as explained
in DynamicPriorityPolling )

This option watches parent directory of the file with


UseFsEventsOnParentDirectory fs.watch (using file system events) thus being low on
CPU but can compromise accuracy.

If environment variable TSC_NONPOLLING_WATCHER is set


to true, watches parent directory of files (just like
default (no value specified) UseFsEventsOnParentDirectory ). Otherwise watch
files using fs.watchFile with 250ms as the timeout
for any file

Configuring directory watching using environment


variable TSC_WATCHDIRECTORY
The watching of directory on platforms that don't support recursive directory watching natively in node, is
supported through recursively creating directory watcher for the child directories using different options se-
lected by TSC_WATCHDIRECTORY . Note that on platforms that support native recursive directory watching
(e.g windows) the value of this environment variable is ignored.

327
typescript

Option Description

Use fs.watchFile to watch the directories


RecursiveDirectoryUsingFsWatchFile and child directories which is a polling watch
(consuming CPU cycles)

Use dynamic polling queue to poll changes to


RecursiveDirectoryUsingDynamicPriorityPolling
the directory and child directories.

Use fs.watch to watch directories and child


default (no value specified)
directories

Go to TOC

328
Integrating with Build Tools

Babel
Install
npm install @babel/cli @babel/core @babel/preset-typescript --save-dev

.babelrc
{
"presets": ["@babel/preset-typescript"]
}

Using Command Line Interface


./node_modules/.bin/babel --out-file bundle.js src/index.ts

package.json
{
"scripts": {
"build": "babel --out-file bundle.js main.ts"
},
}

Execute Babel from the command line


npm run build

Browserify
Install
npm install tsify

Using Command Line Interface

browserify main.ts -p [ tsify --noImplicitAny ] > bundle.js

Using API
var browserify = require("browserify");
var tsify = require("tsify");

browserify()
.add("main.ts")
.plugin("tsify", { noImplicitAny: true })
.bundle()
.pipe(process.stdout);

More details: smrq/tsify

329
typescript

Grunt
Install
npm install grunt-ts

Basic Gruntfile.js
module.exports = function (grunt) {
grunt.initConfig({
ts: {
default: {
src: ["**/*.ts", "!node_modules/**/*.ts"],
},
},
});
grunt.loadNpmTasks("grunt-ts");
grunt.registerTask("default", ["ts"]);
};

More details: TypeStrong/grunt-ts

Gulp
Install
npm install gulp-typescript

Basic gulpfile.js
var gulp = require("gulp");
var ts = require("gulp-typescript");

gulp.task("default", function () {
var tsResult = gulp.src("src/*.ts").pipe(
ts({
noImplicitAny: true,
out: "output.js",
})
);
return tsResult.js.pipe(gulp.dest("built/local"));
});

More details: ivogabe/gulp-typescript

Jspm
Install
npm install -g jspm@beta

Note: Currently TypeScript support in jspm is in 0.16beta

330
Integrating with Build Tools

More details: TypeScriptSamples/jspm

MSBuild
Update project file to include locally installed Microsoft.TypeScript.Default.props (at the top) and
Microsoft.TypeScript.targets (at the bottom) files:

<?xml version="1.0" encoding="utf-8"?>


<Project ToolsVersion="4.0" DefaultTargets="Build"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Include default props at the top -->
<Import

Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\

Condition="Exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\T
/>

<!-- TypeScript configurations go here -->


<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<TypeScriptRemoveComments>false</TypeScriptRemoveComments>
<TypeScriptSourceMap>true</TypeScriptSourceMap>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<TypeScriptRemoveComments>true</TypeScriptRemoveComments>
<TypeScriptSourceMap>false</TypeScriptSourceMap>
</PropertyGroup>

<!-- Include default targets at the bottom -->


<Import

Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\

Condition="Exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\T
/>
</Project>

More details about defining MSBuild compiler options: Setting Compiler Options in MSBuild projects

NuGet
Right-Click -> Manage NuGet Packages
Search for Microsoft.TypeScript.MSBuild
Hit Install
When install is complete, rebuild!

More details can be found at Package Manager Dialog and using nightly builds with NuGet

Rollup
Install
npm install @rollup/plugin-typescript --save-dev

331
typescript

Note that both typescript and tslib are peer dependencies of this plugin that need to be installed
separately.

Usage
Create a rollup.config.js configuration file and import the plugin:

// rollup.config.js
import typescript from '@rollup/plugin-typescript';

export default {
input: 'src/index.ts',
output: {
dir: 'output',
format: 'cjs'
},
plugins: [typescript()]
};

Svelte Compiler
Install
npm install --save-dev svelte-preprocess

Note that typescript is an optional peer dependencies of this plugin and needs to be installed separately.
tslib is not provided either.

You may also consider svelte-check for CLI type checking.

Usage
Create a svelte.config.js configuration file and import the plugin:

// svelte.config.js
import preprocess from 'svelte-preprocess';

const config = {
// Consult https://github.com/sveltejs/svelte-preprocess
// for more information about preprocessors
preprocess: preprocess()
};

export default config;

You can now specify that script blocks are written in TypeScript:

<script lang="ts">

Vite
Vite supports importing .ts files out-of-the-box. It only performs transpilation and not type checking. It
also requires that some compilerOptions have certain values. See the Vite docs for more details.

332
Integrating with Build Tools

Webpack
Install
npm install ts-loader --save-dev

Basic webpack.config.js when using Webpack 5 or 4


const path = require('path');

module.exports = {
entry: './src/index.ts',
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
};

See more details on ts-loader here.

Alternatives:

awesome-typescript-loader

Go to TOC

333
typescript

Project references are a new feature in TypeScript 3.0 that allow you to structure your TypeScript programs
into smaller pieces.

By doing this, you can greatly improve build times, enforce logical separation between components, and or-
ganize your code in new and better ways.

We're also introducing a new mode for tsc , the --build flag, that works hand in hand with project refer-
ences to enable faster TypeScript builds.

An Example Project
Let's look at a fairly normal program and see how project references can help us better organize it. Imagine
you have a project with two modules, converter and units , and a corresponding test file for each:

/
├── src/
│ ├── converter.ts
│ └── units.ts
├── test/
│ ├── converter-tests.ts
│ └── units-tests.ts
└── tsconfig.json

The test files import the implementation files and do some testing:

// converter-tests.ts
import * as converter from "../converter";

assert.areEqual(converter.celsiusToFahrenheit(0), 32);

Previously, this structure was rather awkward to work with if you used a single tsconfig file:

It was possible for the implementation files to import the test files
It wasn't possible to build test and src at the same time without having src appear in the output
folder name, which you probably don't want
Changing just the internals in the implementation files required typechecking the tests again, even
though this wouldn't ever cause new errors
Changing just the tests required typechecking the implementation again, even if nothing changed

You could use multiple tsconfig files to solve some of those problems, but new ones would appear:

There's no built-in up-to-date checking, so you end up always running tsc twice
Invoking tsc twice incurs more startup time overhead
tsc -w can't run on multiple config files at once

Project references can solve all of these problems and more.

334
Project References

What is a Project Reference?


tsconfig.json files have a new top-level property, references . It's an array of objects that specifies
projects to reference:

{
"compilerOptions": {
// The usual
},
"references": [
{ "path": "../src" }
]
}

The path property of each reference can point to a directory containing a tsconfig.json file, or to the
config file itself (which may have any name).

When you reference a project, new things happen:

Importing modules from a referenced project will instead load its output declaration file ( .d.ts )
If the referenced project produces an outFile , the output file .d.ts file's declarations will be visible in
this project
Build mode (see below) will automatically build the referenced project if needed

By separating into multiple projects, you can greatly improve the speed of typechecking and compiling, re-
duce memory usage when using an editor, and improve enforcement of the logical groupings of your
program.

composite
Referenced projects must have the new composite setting enabled. This setting is needed to ensure
TypeScript can quickly determine where to find the outputs of the referenced project. Enabling the compos‐
ite flag changes a few things:

The rootDir setting, if not explicitly set, defaults to the directory containing the tsconfig file
All implementation files must be matched by an include pattern or listed in the files array. If this
constraint is violated, tsc will inform you which files weren't specified
declaration must be turned on

declarationMap s
We've also added support for declaration source maps. If you enable declarationMap , you'll be able to use
editor features like "Go to Definition" and Rename to transparently navigate and edit code across project
boundaries in supported editors.

prepend with outFile


You can also enable prepending the output of a dependency using the prepend option in a reference:

335
typescript

"references": [
{ "path": "../utils", "prepend": true }
]

Prepending a project will include the project's output above the output of the current project. All output files
( .js , .d.ts , .js.map , .d.ts.map ) will be emitted correctly.

tsc will only ever use existing files on disk to do this process, so it's possible to create a project where a
correct output file can't be generated because some project's output would be present more than once in
the resulting file. For example:

A
^ ^
/ \
B C
^ ^
\ /
D

It's important in this situation to not prepend at each reference, because you'll end up with two copies of A
in the output of D - this can lead to unexpected results.

Caveats for Project References


Project references have a few trade-offs you should be aware of.

Because dependent projects make use of .d.ts files that are built from their dependencies, you'll either
have to check in certain build outputs or build a project after cloning it before you can navigate the project
in an editor without seeing spurious errors.

When using VS Code (since TS 3.7) we have a behind-the-scenes in-memory .d.ts generation process
that should be able to mitigate this, but it has some perf implications. For very large composite projects you
might want to disable this using disableSourceOfProjectReferenceRedirect option.

Additionally, to preserve compatibility with existing build workflows, tsc will not automatically build depen-
dencies unless invoked with the --build switch. Let's learn more about --build .

Build Mode for TypeScript


A long-awaited feature is smart incremental builds for TypeScript projects. In 3.0 you can use the --build
flag with tsc . This is effectively a new entry point for tsc that behaves more like a build orchestrator
than a simple compiler.

Running tsc --build ( tsc -b for short) will do the following:

Find all referenced projects


Detect if they are up-to-date
Build out-of-date projects in the correct order

336
Project References

You can provide tsc -b with multiple config file paths (e.g. tsc -b src test ). Just like tsc -p , speci-
fying the config file name itself is unnecessary if it's named tsconfig.json .

tsc -b Commandline
You can specify any number of config files:

> tsc -b # Use the tsconfig.json in the current


directory
> tsc -b src # Use src/tsconfig.json
> tsc -b foo/prd.tsconfig.json bar # Use foo/prd.tsconfig.json and
bar/tsconfig.json

Don't worry about ordering the files you pass on the commandline - tsc will re-order them if needed so
that dependencies are always built first.

There are also some flags specific to tsc -b :

--verbose : Prints out verbose logging to explain what's going on (may be combined with any other
flag)
--dry : Shows what would be done but doesn't actually build anything
--clean : Deletes the outputs of the specified projects (may be combined with --dry )
--force : Act as if all projects are out of date
--watch : Watch mode (may not be combined with any flag except --verbose )

Caveats
Normally, tsc will produce outputs ( .js and .d.ts ) in the presence of syntax or type errors, unless
noEmitOnError is on. Doing this in an incremental build system would be very bad - if one of your out-of-
date dependencies had a new error, you'd only see it once because a subsequent build would skip building
the now up-to-date project. For this reason, tsc -b effectively acts as if noEmitOnError is enabled for all
projects.

If you check in any build outputs ( .js , .d.ts , .d.ts.map , etc.), you may need to run a --force build
after certain source control operations depending on whether your source control tool preserves timestamps
between the local copy and the remote copy.

MSBuild
If you have an msbuild project, you can enable build mode by adding

<TypeScriptBuildMode>true</TypeScriptBuildMode>

to your proj file. This will enable automatic incremental build as well as cleaning.

Note that as with tsconfig.json / -p , existing TypeScript project properties will not be respected - all
settings should be managed using your tsconfig file.

337
typescript

Some teams have set up msbuild-based workflows wherein tsconfig files have the same implicit graph or-
dering as the managed projects they are paired with. If your solution is like this, you can continue to use
msbuild with tsc -p along with project references; these are fully interoperable.

Guidance
Overall Structure
With more tsconfig.json files, you'll usually want to use Configuration file inheritance to centralize your
common compiler options. This way you can change a setting in one file rather than having to edit multiple
files.

Another good practice is to have a "solution" tsconfig.json file that simply has references to all of
your leaf-node projects and sets files to an empty array (otherwise the solution file will cause double
compilation of files). Note that starting with 3.0, it is no longer an error to have an empty files array if
you have at least one reference in a tsconfig.json file.

This presents a simple entry point; e.g. in the TypeScript repo we simply run tsc -b src to build all end-
points because we list all the subprojects in src/tsconfig.json

You can see these patterns in the TypeScript repo - see src/tsconfig_base.json , src/tsconfig.json ,
and src/tsc/tsconfig.json as key examples.

Structuring for relative modules


In general, not much is needed to transition a repo using relative modules. Simply place a tsconfig.json
file in each subdirectory of a given parent folder, and add reference s to these config files to match the in-
tended layering of the program. You will need to either set the outDir to an explicit subfolder of the output
folder, or set the rootDir to the common root of all project folders.

Structuring for outFiles


Layout for compilations using outFile is more flexible because relative paths don't matter as much. One
thing to keep in mind is that you'll generally want to not use prepend until the "last" project - this will im-
prove build times and reduce the amount of I/O needed in any given build. The TypeScript repo itself is a
good reference here - we have some "library" projects and some "endpoint" projects; "endpoint" projects
are kept as small as possible and pull in only the libraries they need.

Go to TOC

338
What is a tsconfig.json

Overview
The presence of a tsconfig.json file in a directory indicates that the directory is the root of a TypeScript
project. The tsconfig.json file specifies the root files and the compiler options required to compile the
project.

JavaScript projects can use a jsconfig.json file instead, which acts almost the same but has some
JavaScript-related compiler flags enabled by default.

A project is compiled in one of the following ways:

Using tsconfig.json or jsconfig.json


By invoking tsc with no input files, in which case the compiler searches for the tsconfig.json file start-
ing in the current directory and continuing up the parent directory chain.
By invoking tsc with no input files and a --project (or just -p ) command line option that specifies the
path of a directory containing a tsconfig.json file, or a path to a valid .json file containing the
configurations.

When input files are specified on the command line, tsconfig.json files are ignored.

Examples
Example tsconfig.json files:

Using the files property

{
"compilerOptions": {
"module": "commonjs",
"noImplicitAny": true,
"removeComments": true,
"preserveConstEnums": true,
"sourceMap": true
},
"files": [
"core.ts",
"sys.ts",
"types.ts",
"scanner.ts",
"parser.ts",
"utilities.ts",
"binder.ts",
"checker.ts",
"emitter.ts",
"program.ts",
"commandLineParser.ts",
"tsc.ts",
"diagnosticInformationMap.generated.ts"
]
}

339
typescript

Using the include and exclude properties

{
"compilerOptions": {
"module": "system",
"noImplicitAny": true,
"removeComments": true,
"preserveConstEnums": true,
"outFile": "../../built/local/tsc.js",
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "**/*.spec.ts"]
}

TSConfig Bases
Depending on the JavaScript runtime environment which you intend to run your code in, there may be a
base configuration which you can use at github.com/tsconfig/bases. These are tsconfig.json files which
your project extends from which simplifies your tsconfig.json by handling the runtime support.

For example, if you were writing a project which uses Node.js version 12 and above, then you could use the
npm module @tsconfig/node12 :

{
"extends": "@tsconfig/node12/tsconfig.json",

"compilerOptions": {
"preserveConstEnums": true
},

"include": ["src/**/*"],
"exclude": ["node_modules", "**/*.spec.ts"]
}

This lets your tsconfig.json focus on the unique choices for your project, and not all of the runtime me-
chanics. There are a few tsconfig bases already, and we're hoping the community can add more for different
environments.

Recommended
Node 10
Node 12
Node 14
Node 16
Deno
React Native
Svelte

340
What is a tsconfig.json

Details
The "compilerOptions" property can be omitted, in which case the compiler's defaults are used. See our
full list of supported Compiler Options.

TSConfig Reference
To learn more about the hundreds of configuration options in the TSConfig Reference.

Schema
The tsconfig.json Schema can be found at the JSON Schema Store.

Go to TOC

341
typescript

This page lists some of the more advanced ways in which you can model types, it works in tandem with the
Utility Types doc which includes types which are included in TypeScript and available globally.

Type Guards and Differentiating Types


Union types are useful for modeling situations when values can overlap in the types they can take on. What
happens when we need to know specifically whether we have a Fish ? A common idiom in JavaScript to dif-
ferentiate between two possible values is to check for the presence of a member. As we mentioned, you can
only access members that are guaranteed to be in all the constituents of a union type.

// @errors: 2339
type Fish = { swim: () => void };
type Bird = { fly: () => void };
declare function getSmallPet(): Fish | Bird;
// ---cut---
let pet = getSmallPet();

// You can use the 'in' operator to check


if ("swim" in pet) {
pet.swim();
}
// However, you cannot use property access
if (pet.fly) {
pet.fly();
}

To get the same code working via property accessors, we'll need to use a type assertion:

type Fish = { swim: () => void };


type Bird = { fly: () => void };
declare function getSmallPet(): Fish | Bird;
// ---cut---
let pet = getSmallPet();
let fishPet = pet as Fish;
let birdPet = pet as Bird;

if (fishPet.swim) {
fishPet.swim();
} else if (birdPet.fly) {
birdPet.fly();
}

This isn't the sort of code you would want in your codebase however.

User-Defined Type Guards


It would be much better if once we performed the check, we could know the type of pet within each
branch.

It just so happens that TypeScript has something called a type guard. A type guard is some expression that
performs a runtime check that guarantees the type in some scope.

342
Advanced Types

Using type predicates


To define a type guard, we simply need to define a function whose return type is a type predicate:

type Fish = { swim: () => void };


type Bird = { fly: () => void };
declare function getSmallPet(): Fish | Bird;
// ---cut---
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}

pet is Fish is our type predicate in this example. A predicate takes the form parameterName is Type ,
where parameterName must be the name of a parameter from the current function signature.

Any time isFish is called with some variable, TypeScript will narrow that variable to that specific type if
the original type is compatible.

type Fish = { swim: () => void };


type Bird = { fly: () => void };
declare function getSmallPet(): Fish | Bird;
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
// ---cut---
// Both calls to 'swim' and 'fly' are now okay.
let pet = getSmallPet();

if (isFish(pet)) {
pet.swim();
} else {
pet.fly();
}

Notice that TypeScript not only knows that pet is a Fish in the if branch; it also knows that in the
else branch, you don't have a Fish , so you must have a Bird .

You may use the type guard isFish to filter an array of Fish | Bird and obtain an array of Fish :

// @errors: 2345
type Fish = { swim: () => void };
type Bird = { fly: () => void };
declare function getSmallPet(): Fish | Bird;
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
// ---cut---
const zoo: (Fish | Bird)[] = [getSmallPet(), getSmallPet(), getSmallPet()];
const underWater1: Fish[] = zoo.filter(isFish);
// or, equivalently
const underWater2: Fish[] = zoo.filter<Fish>(isFish);
const underWater3: Fish[] = zoo.filter<Fish>((pet) => isFish(pet));

Using the in operator


The in operator also acts as a narrowing expression for types.

343
typescript

For a n in x expression, where n is a string literal or string literal type and x is a union type, the "true"
branch narrows to types which have an optional or required property n , and the "false" branch narrows to
types which have an optional or missing property n .

type Fish = { swim: () => void };


type Bird = { fly: () => void };
// ---cut---
function move(pet: Fish | Bird) {
if ("swim" in pet) {
return pet.swim();
}
return pet.fly();
}

typeof type guards


Let's go back and write the code for a version of padLeft which uses union types. We could write it with
type predicates as follows:

function isNumber(x: any): x is number {


return typeof x === "number";
}

function isString(x: any): x is string {


return typeof x === "string";
}

function padLeft(value: string, padding: string | number) {


if (isNumber(padding)) {
return Array(padding + 1).join(" ") + value;
}
if (isString(padding)) {
return padding + value;
}
throw new Error(`Expected string or number, got '${padding}'.`);
}

However, having to define a function to figure out if a type is a primitive is kind of a pain. Luckily, you don't
need to abstract typeof x === "number" into its own function because TypeScript will recognize it as a
type guard on its own. That means we could just write these checks inline.

function padLeft(value: string, padding: string | number) {


if (typeof padding === "number") {
return Array(padding + 1).join(" ") + value;
}
if (typeof padding === "string") {
return padding + value;
}
throw new Error(`Expected string or number, got '${padding}'.`);
}

344
Advanced Types

These typeof type guards are recognized in two different forms: typeof v === "typename" and typeof
v !== "typename" , where "typename" can be one of typeof operator's return values ( "undefined" ,
"number" , "string" , "boolean" , "bigint" , "symbol" , "object" , or "function" ). While
TypeScript won't stop you from comparing to other strings, the language won't recognize those expressions
as type guards.

instanceof type guards


If you've read about typeof type guards and are familiar with the instanceof operator in JavaScript, you
probably have some idea of what this section is about.

instanceof type guards are a way of narrowing types using their constructor function. For instance, let's
borrow our industrial strength string-padder example from earlier:

interface Padder {
getPaddingString(): string;
}

class SpaceRepeatingPadder implements Padder {


constructor(private numSpaces: number) {}
getPaddingString() {
return Array(this.numSpaces + 1).join(" ");
}
}

class StringPadder implements Padder {


constructor(private value: string) {}
getPaddingString() {
return this.value;
}
}

function getRandomPadder() {
return Math.random() < 0.5
? new SpaceRepeatingPadder(4)
: new StringPadder(" ");
}

let padder: Padder = getRandomPadder();


// ^?

if (padder instanceof SpaceRepeatingPadder) {


padder;
// ^?
}
if (padder instanceof StringPadder) {
padder;
// ^?
}

The right side of the instanceof needs to be a constructor function, and TypeScript will narrow down to:

1. the type of the function's prototype property if its type is not any
2. the union of types returned by that type's construct signatures

345
typescript

in that order.

Nullable types
TypeScript has two special types, null and undefined , that have the values null and undefined respec-
tively. We mentioned these briefly in the Basic Types section.

By default, the type checker considers null and undefined assignable to anything. Effectively, null and
undefined are valid values of every type. That means it's not possible to stop them from being assigned to
any type, even when you would like to prevent it. The inventor of null , Tony Hoare, calls this his "billion
dollar mistake".

The strictNullChecks flag fixes this: when you declare a variable, it doesn't automatically include null
or undefined . You can include them explicitly using a union type:

// @errors: 2322
let exampleString = "foo";
exampleString = null;

let stringOrNull: string | null = "bar";


stringOrNull = null;

stringOrNull = undefined;

Note that TypeScript treats null and undefined differently in order to match JavaScript semantics.
string | null is a different type than string | undefined and string | undefined | null .

From TypeScript 3.7 and onwards, you can use optional chaining to simplify working with nullable types.

Optional parameters and properties


With strictNullChecks , an optional parameter automatically adds | undefined :

// @errors: 2345
function f(x: number, y?: number) {
return x + (y ?? 0);
}

f(1, 2);
f(1);
f(1, undefined);
f(1, null);

The same is true for optional properties:

// @strict: false
// @strictNullChecks: true
// @errors: 2322
class C {
a: number;
b?: number;
}

let c = new C();

346
Advanced Types

c.a = 12;
c.a = undefined;
c.b = 13;
c.b = undefined;
c.b = null;

Type guards and type assertions


Since nullable types are implemented with a union, you need to use a type guard to get rid of the null .
Fortunately, this is the same code you'd write in JavaScript:

function f(stringOrNull: string | null): string {


if (stringOrNull === null) {
return "default";
} else {
return stringOrNull;
}
}

The null elimination is pretty obvious here, but you can use terser operators too:

function f(stringOrNull: string | null): string {


return stringOrNull ?? "default";
}

In cases where the compiler can't eliminate null or undefined , you can use the type assertion operator
to manually remove them. The syntax is postfix ! : identifier! removes null and undefined from
the type of identifier :

// @errors: 2532
function getUser(id: string): UserAccount | undefined {
return {} as any;
}
// ---cut---
interface UserAccount {
id: number;
email?: string;
}

const user = getUser("admin");


user.id;

if (user) {
user.email.length;
}

// Instead if you are sure that these objects or fields exist, the
// postfix ! lets you short circuit the nullability
user!.email!.length;

Type Aliases
Type aliases create a new name for a type. Type aliases are sometimes similar to interfaces, but can name
primitives, unions, tuples, and any other types that you'd otherwise have to write by hand.

347
typescript

type Second = number;

let timeInSecond: number = 10;


let time: Second = 10;

Aliasing doesn't actually create a new type - it creates a new name to refer to that type. Aliasing a primitive
is not terribly useful, though it can be used as a form of documentation.

Just like interfaces, type aliases can also be generic - we can just add type parameters and use them on the
right side of the alias declaration:

type Container<T> = { value: T };

We can also have a type alias refer to itself in a property:

type Tree<T> = {
value: T;
left?: Tree<T>;
right?: Tree<T>;
};

Together with intersection types, we can make some pretty mind-bending types:

declare function getDriversLicenseQueue(): LinkedList<Person>;


// ---cut---
type LinkedList<Type> = Type & { next: LinkedList<Type> };

interface Person {
name: string;
}

let people = getDriversLicenseQueue();


people.name;
people.next.name;
people.next.next.name;
people.next.next.next.name;
// ^?

Interfaces vs. Type Aliases


As we mentioned, type aliases can act sort of like interfaces; however, there are some subtle differences.

Almost all features of an interface are available in type , the key distinction is that a type cannot be re-
opened to add new properties vs an interface which is always extendable.

Interface Type

Extending an interface Extending a type via intersections

348
Advanced Types

interface Animal { type Animal = {


name: string name: string
} }

interface Bear extends Animal { type Bear = Animal & {


honey: boolean honey: Boolean
} }

const bear = getBear() const bear = getBear();


bear.name bear.name;
bear.honey bear.honey;

Adding new fields to an existing interface A type cannot be changed after being created

interface Window { type Window = {


title: string title: string
} }

interface Window { type Window = {


ts: import("typescript") ts: import("typescript")
} }

const src = 'const a = "Hello World"'; // Error: Duplicate identifier 'Window'.


window.ts.transpileModule(src, {});

Because an interface more closely maps how JavaScript objects work by being open to extension, we rec-
ommend using an interface over a type alias when possible.

On the other hand, if you can't express some shape with an interface and you need to use a union or tuple
type, type aliases are usually the way to go.

Enum Member Types


As mentioned in our section on enums, enum members have types when every member is literal-initialized.

349
typescript

Much of the time when we talk about "singleton types", we're referring to both enum member types as well
as numeric/string literal types, though many users will use "singleton types" and "literal types"
interchangeably.

Polymorphic this types


A polymorphic this type represents a type that is the subtype of the containing class or interface. This is
called F-bounded polymorphism, a lot of people know it as the fluent API pattern. This makes hierarchical
fluent interfaces much easier to express, for example. Take a simple calculator that returns this after each
operation:

class BasicCalculator {
public constructor(protected value: number = 0) {}
public currentValue(): number {
return this.value;
}
public add(operand: number): this {
this.value += operand;
return this;
}
public multiply(operand: number): this {
this.value *= operand;
return this;
}
// ... other operations go here ...
}

let v = new BasicCalculator(2).multiply(5).add(1).currentValue();

Since the class uses this types, you can extend it and the new class can use the old methods with no
changes.

class BasicCalculator {
public constructor(protected value: number = 0) {}
public currentValue(): number {
return this.value;
}
public add(operand: number): this {
this.value += operand;
return this;
}
public multiply(operand: number): this {
this.value *= operand;
return this;
}
// ... other operations go here ...
}
// ---cut---
class ScientificCalculator extends BasicCalculator {
public constructor(value = 0) {
super(value);
}
public sin() {
this.value = Math.sin(this.value);
return this;
}
// ... other operations go here ...

350
Advanced Types

let v = new ScientificCalculator(2).multiply(5).sin().add(1).currentValue();

Without this types, ScientificCalculator would not have been able to extend BasicCalculator and
keep the fluent interface. multiply would have returned BasicCalculator , which doesn't have the sin
method. However, with this types, multiply returns this , which is ScientificCalculator here.

Index types
With index types, you can get the compiler to check code that uses dynamic property names. For example,
a common JavaScript pattern is to pick a subset of properties from an object:

function pluck(o, propertyNames) {


return propertyNames.map((n) => o[n]);
}

Here's how you would write and use this function in TypeScript, using the index type query and indexed
access operators:

function pluck<T, K extends keyof T>(o: T, propertyNames: K[]): T[K][] {


return propertyNames.map((n) => o[n]);
}

interface Car {
manufacturer: string;
model: string;
year: number;
}

let taxi: Car = {


manufacturer: "Toyota",
model: "Camry",
year: 2014,
};

// Manufacturer and model are both of type string,


// so we can pluck them both into a typed string array
let makeAndModel: string[] = pluck(taxi, ["manufacturer", "model"]);

// If we try to pluck model and year, we get an


// array of a union type: (string | number)[]
let modelYear = pluck(taxi, ["model", "year"]);

The compiler checks that manufacturer and model are actually properties on Car . The example intro-
duces a couple of new type operators. First is keyof T , the index type query operator. For any type T ,
keyof T is the union of known, public property names of T . For example:

interface Car {
manufacturer: string;
model: string;
year: number;
}

351
typescript

// ---cut---
let carProps: keyof Car;
// ^?

keyof Car is completely interchangeable with "manufacturer" | "model" | "year" . The difference is
that if you add another property to Car , say ownersAddress: string , then keyof Car will automatically
update to be "manufacturer" | "model" | "year" | "ownersAddress" . And you can use keyof in
generic contexts like pluck , where you can't possibly know the property names ahead of time. That means
the compiler will check that you pass the right set of property names to pluck :

// error, Type '"unknown"' is not assignable to type '"manufacturer" | "model" |


"year"'
pluck(taxi, ["year", "unknown"]);

The second operator is T[K] , the indexed access operator. Here, the type syntax reflects the expression
syntax. That means that taxi["manufacturer"] has the type Car["manufacturer"] — which in our ex-
ample is just string . However, just like index type queries, you can use T[K] in a generic context, which
is where its real power comes to life. You just have to make sure that the type variable K extends keyof
T . Here's another example with a function named getProperty .

function getProperty<T, K extends keyof T>(o: T, propertyName: K): T[K] {


return o[propertyName]; // o[propertyName] is of type T[K]
}

In getProperty , o: T and propertyName: K , so that means o[propertyName]: T[K] . Once you re-
turn the T[K] result, the compiler will instantiate the actual type of the key, so the return type of get‐
Property will vary according to which property you request.

// @errors: 2345
function getProperty<T, K extends keyof T>(o: T, propertyName: K): T[K] {
return o[propertyName]; // o[propertyName] is of type T[K]
}
interface Car {
manufacturer: string;
model: string;
year: number;
}
let taxi: Car = {
manufacturer: "Toyota",
model: "Camry",
year: 2014,
};
// ---cut---
let manufacturer: string = getProperty(taxi, "manufacturer");
let year: number = getProperty(taxi, "year");

let unknown = getProperty(taxi, "unknown");

352
Advanced Types

Index types and index signatures


keyof and T[K] interact with index signatures. An index signature parameter type must be 'string' or
'number'. If you have a type with a string index signature, keyof T will be string | number (and not
just string , since in JavaScript you can access an object property either by using strings ( object["42"] )
or numbers ( object[42] )). And T[string] is just the type of the index signature:

interface Dictionary<T> {
[key: string]: T;
}
let keys: keyof Dictionary<number>;
// ^?
let value: Dictionary<number>["foo"];
// ^?

If you have a type with a number index signature, keyof T will just be number .

// @errors: 2339
interface Dictionary<T> {
[key: number]: T;
}

let keys: keyof Dictionary<number>;


// ^?
let numberValue: Dictionary<number>[42];
// ^?
let value: Dictionary<number>["foo"];

Mapped types
A common task is to take an existing type and make each of its properties optional:

interface PersonSubset {
name?: string;
age?: number;
}

Or we might want a readonly version:

interface PersonReadonly {
readonly name: string;
readonly age: number;
}

This happens often enough in JavaScript that TypeScript provides a way to create new types based on old
types — mapped types. In a mapped type, the new type transforms each property in the old type in the
same way. For example, you can make all properties optional or of a type readonly . Here are a couple of
examples:

type Partial<T> = {
[P in keyof T]?: T[P];
};

353
typescript

// @noErrors
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};

And to use it:

type Person = {
name: string;
age: number;
};
// ---cut---
type PersonPartial = Partial<Person>;
// ^?
type ReadonlyPerson = Readonly<Person>;
// ^?

Note that this syntax describes a type rather than a member. If you want to add members, you can use an
intersection type:

// @errors: 2693 1005 1128 7061


// Use this:
type PartialWithNewMember<T> = {
[P in keyof T]?: T[P];
} & { newMember: boolean }

// This is an error!
type WrongPartialWithNewMember<T> = {
[P in keyof T]?: T[P];
newMember: boolean;
}

Let's take a look at the simplest mapped type and its parts:

type Keys = "option1" | "option2";


type Flags = { [K in Keys]: boolean };

The syntax resembles the syntax for index signatures with a for .. in inside. There are three parts:

1. The type variable K , which gets bound to each property in turn.


2. The string literal union Keys , which contains the names of properties to iterate over.
3. The resulting type of the property.

In this simple example, Keys is a hard-coded list of property names and the property type is always bool‐
ean , so this mapped type is equivalent to writing:

type Flags = {
option1: boolean;
option2: boolean;
};

Real applications, however, look like Readonly or Partial above. They're based on some existing type,
and they transform the properties in some way. That's where keyof and indexed access types come in:

354
Advanced Types

type Person = {
name: string;
age: number;
};
// ---cut---
type NullablePerson = { [P in keyof Person]: Person[P] | null };
// ^?
type PartialPerson = { [P in keyof Person]?: Person[P] };
// ^?

But it's more useful to have a general version.

type Nullable<T> = { [P in keyof T]: T[P] | null };


type Partial<T> = { [P in keyof T]?: T[P] };

In these examples, the properties list is keyof T and the resulting type is some variant of T[P] . This is a
good template for any general use of mapped types. That's because this kind of transformation is homo-
morphic, which means that the mapping applies only to properties of T and no others. The compiler knows
that it can copy all the existing property modifiers before adding any new ones. For example, if
Person.name was readonly, Partial<Person>.name would be readonly and optional.

Here's one more example, in which T[P] is wrapped in a Proxy<T> class:

// @noErrors
type Proxy<T> = {
get(): T;
set(value: T): void;
};

type Proxify<T> = {
[P in keyof T]: Proxy<T[P]>;
};

function proxify<T>(o: T): Proxify<T> {


// ... wrap proxies ...
}

let props = { rooms: 4 };


let proxyProps = proxify(props);
// ^?

Note that Readonly<T> and Partial<T> are so useful, they are included in TypeScript's standard library
along with Pick and Record :

type Pick<T, K extends keyof T> = {


[P in K]: T[P];
};

type Record<K extends keyof any, T> = {


[P in K]: T;
};

Readonly , Partial and Pick are homomorphic whereas Record is not. One clue that Record is not
homomorphic is that it doesn't take an input type to copy properties from:

type ThreeStringProps = Record<"prop1" | "prop2" | "prop3", string>;

355
typescript

Non-homomorphic types are essentially creating new properties, so they can't copy property modifiers from
anywhere.

Note that keyof any represents the type of any value that can be used as an index to an object. In other-
words, keyof any is currently equal to string | number | symbol .

Inference from mapped types


Now that you know how to wrap the properties of a type, the next thing you'll want to do is unwrap them.
Fortunately, that's pretty easy:

type Proxy<T> = {
get(): T;
set(value: T): void;
};

type Proxify<T> = {
[P in keyof T]: Proxy<T[P]>;
};

function proxify<T>(o: T): Proxify<T> {


return {} as any;
}

let props = { rooms: 4 };


let proxyProps = proxify(props);
// ---cut---
function unproxify<T>(t: Proxify<T>): T {
let result = {} as T;
for (const k in t) {
result[k] = t[k].get();
}
return result;
}

let originalProps = unproxify(proxyProps);


// ^?

Note that this unwrapping inference only works on homomorphic mapped types. If the mapped type is not
homomorphic you'll have to give an explicit type parameter to your unwrapping function.

Conditional Types
A conditional type selects one of two possible types based on a condition expressed as a type relationship
test:

T extends U ? X : Y

The type above means when T is assignable to U the type is X , otherwise the type is Y .

356
Advanced Types

A conditional type T extends U ? X : Y is either resolved to X or Y , or deferred because the condition


depends on one or more type variables. When T or U contains type variables, whether to resolve to X or
Y , or to defer, is determined by whether or not the type system has enough information to conclude that
T is always assignable to U .

As an example of some types that are immediately resolved, we can take a look at the following example:

declare function f<T extends boolean>(x: T): T extends true ? string : number;

// Type is 'string | number'


let x = f(Math.random() < 0.5);
// ^?

Another example would be the TypeName type alias, which uses nested conditional types:

type TypeName<T> = T extends string


? "string"
: T extends number
? "number"
: T extends boolean
? "boolean"
: T extends undefined
? "undefined"
: T extends Function
? "function"
: "object";

type T0 = TypeName<string>;
// ^?
type T1 = TypeName<"a">;
// ^?
type T2 = TypeName<true>;
// ^?
type T3 = TypeName<() => void>;
// ^?
type T4 = TypeName<string[]>;
// ^?

But as an example of a place where conditional types are deferred - where they stick around instead of
picking a branch - would be in the following:

interface Foo {
propA: boolean;
propB: boolean;
}

declare function f<T>(x: T): T extends Foo ? string : number;

function foo<U>(x: U) {
// Has type 'U extends Foo ? string : number'
let a = f(x);

// This assignment is allowed though!


let b: string | number = a;
}

357
typescript

In the above, the variable a has a conditional type that hasn't yet chosen a branch. When another piece of
code ends up calling foo , it will substitute in U with some other type, and TypeScript will re-evaluate the
conditional type, deciding whether it can actually pick a branch.

In the meantime, we can assign a conditional type to any other target type as long as each branch of the
conditional is assignable to that target. So in our example above we were able to assign U extends Foo ?
string : number to string | number since no matter what the conditional evaluates to, it's known to be
either string or number .

Distributive conditional types


Conditional types in which the checked type is a naked type parameter are called distributive conditional
types. Distributive conditional types are automatically distributed over union types during instantiation. For
example, an instantiation of T extends U ? X : Y with the type argument A | B | C for T is resolved
as (A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y) .

Example

type TypeName<T> = T extends string


? "string"
: T extends number
? "number"
: T extends boolean
? "boolean"
: T extends undefined
? "undefined"
: T extends Function
? "function"
: "object";
// ---cut---
type T5 = TypeName<string | (() => void)>;
// ^?
type T6 = TypeName<string | string[] | undefined>;
// ^?
type T7 = TypeName<string[] | number[]>;
// ^?

In instantiations of a distributive conditional type T extends U ? X : Y , references to T within the condi-


tional type are resolved to individual constituents of the union type (i.e. T refers to the individual con-
stituents after the conditional type is distributed over the union type). Furthermore, references to T within
X have an additional type parameter constraint U (i.e. T is considered assignable to U within X ).

Example

type BoxedValue<T> = { value: T };


type BoxedArray<T> = { array: T[] };
type Boxed<T> = T extends any[] ? BoxedArray<T[number]> : BoxedValue<T>;

type T1 = Boxed<string>;
// ^?
type T2 = Boxed<number[]>;

358
Advanced Types

// ^?
type T3 = Boxed<string | number[]>;
// ^?

Notice that T has the additional constraint any[] within the true branch of Boxed<T> and it is therefore
possible to refer to the element type of the array as T[number] . Also, notice how the conditional type is
distributed over the union type in the last example.

The distributive property of conditional types can conveniently be used to filter union types:

// @errors: 2300 2322


// Remove types from T that are assignable to U
type Diff<T, U> = T extends U ? never : T;
// Remove types from T that are not assignable to U
type Filter<T, U> = T extends U ? T : never;

type T1 = Diff<"a" | "b" | "c" | "d", "a" | "c" | "f">;


// ^?
type T2 = Filter<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "a" | "c"
// ^?
type T3 = Diff<string | number | (() => void), Function>; // string | number
// ^?
type T4 = Filter<string | number | (() => void), Function>; // () => void
// ^?

// Remove null and undefined from T


type NotNullable<T> = Diff<T, null | undefined>;

type T5 = NotNullable<string | number | undefined>;


// ^?
type T6 = NotNullable<string | string[] | null | undefined>;
// ^?

function f1<T>(x: T, y: NotNullable<T>) {


x = y;
y = x;
}

function f2<T extends string | undefined>(x: T, y: NotNullable<T>) {


x = y;
y = x;
let s1: string = x;
let s2: string = y;
}

Conditional types are particularly useful when combined with mapped types:

type FunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];
type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>>;

type NonFunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends Function ? never : K;
}[keyof T];
type NonFunctionProperties<T> = Pick<T, NonFunctionPropertyNames<T>>;

interface Part {
id: number;

359
typescript

name: string;
subparts: Part[];
updatePart(newName: string): void;
}

type T1 = FunctionPropertyNames<Part>;
// ^?
type T2 = NonFunctionPropertyNames<Part>;
// ^?
type T3 = FunctionProperties<Part>;
// ^?
type T4 = NonFunctionProperties<Part>;
// ^?

Note, conditional types are not permitted to reference themselves recursively. For example the following is
an error.

Example

// @errors: 2456 2315


type ElementType<T> = T extends any[] ? ElementType<T[number]> : T; // Error

Type inference in conditional types


Within the extends clause of a conditional type, it is now possible to have infer declarations that intro-
duce a type variable to be inferred. Such inferred type variables may be referenced in the true branch of the
conditional type. It is possible to have multiple infer locations for the same type variable.

For example, the following extracts the return type of a function type:

// @noErrors
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;

Conditional types can be nested to form a sequence of pattern matches that are evaluated in order:

type Unpacked<T> = T extends (infer U)[]


? U
: T extends (...args: any[]) => infer U
? U
: T extends Promise<infer U>
? U
: T;

type T0 = Unpacked<string>;
// ^?
type T1 = Unpacked<string[]>;
// ^?
type T2 = Unpacked<() => string>;
// ^?
type T3 = Unpacked<Promise<string>>;
// ^?
type T4 = Unpacked<Promise<string>[]>;
// ^?
type T5 = Unpacked<Unpacked<Promise<string>[]>>;
// ^?

360
Advanced Types

The following example demonstrates how multiple candidates for the same type variable in co-variant posi-
tions causes a union type to be inferred:

type Foo<T> = T extends { a: infer U; b: infer U } ? U : never;

type T1 = Foo<{ a: string; b: string }>;


// ^?
type T2 = Foo<{ a: string; b: number }>;
// ^?

Likewise, multiple candidates for the same type variable in contra-variant positions causes an intersection
type to be inferred:

type Bar<T> = T extends { a: (x: infer U) => void; b: (x: infer U) => void }
? U
: never;

type T1 = Bar<{ a: (x: string) => void; b: (x: string) => void }>;
// ^?
type T2 = Bar<{ a: (x: string) => void; b: (x: number) => void }>;
// ^?

When inferring from a type with multiple call signatures (such as the type of an overloaded function), infer-
ences are made from the last signature (which, presumably, is the most permissive catch-all case). It is not
possible to perform overload resolution based on a list of argument types.

declare function foo(x: string): number;


declare function foo(x: number): string;
declare function foo(x: string | number): string | number;

type T1 = ReturnType<typeof foo>;


// ^?

It is not possible to use infer declarations in constraint clauses for regular type parameters:

// @errors: 1338 2304


type ReturnedType<T extends (...args: any[]) => infer R> = R;

However, much the same effect can be obtained by erasing the type variables in the constraint and instead
specifying a conditional type:

// @noErrors
type AnyFunction = (...args: any[]) => any;
type ReturnType<T extends AnyFunction> = T extends (...args: any[]) => infer R
? R
: any;

Predefined conditional types


TypeScript adds several predefined conditional types, you can find the full list and examples in Utility Types.

Go to TOC

361
typescript

Introduction
Some of the unique concepts in TypeScript describe the shape of JavaScript objects at the type level. One
example that is especially unique to TypeScript is the concept of 'declaration merging'. Understanding this
concept will give you an advantage when working with existing JavaScript. It also opens the door to more
advanced abstraction concepts.

For the purposes of this article, "declaration merging" means that the compiler merges two separate decla-
rations declared with the same name into a single definition. This merged definition has the features of both
of the original declarations. Any number of declarations can be merged; it's not limited to just two
declarations.

Basic Concepts
In TypeScript, a declaration creates entities in at least one of three groups: namespace, type, or value.
Namespace-creating declarations create a namespace, which contains names that are accessed using a dot-
ted notation. Type-creating declarations do just that: they create a type that is visible with the declared
shape and bound to the given name. Lastly, value-creating declarations create values that are visible in the
output JavaScript.

Declaration Type Namespace Type Value

Namespace X X

Class X X

Enum X X

Interface X

Type Alias X

Function X

Variable X

Understanding what is created with each declaration will help you understand what is merged when you
perform a declaration merge.

Merging Interfaces
The simplest, and perhaps most common, type of declaration merging is interface merging. At the most ba-
sic level, the merge mechanically joins the members of both declarations into a single interface with the
same name.

interface Box {
height: number;
width: number;
}

362
Declaration Merging

interface Box {
scale: number;
}

let box: Box = { height: 5, width: 6, scale: 10 };

Non-function members of the interfaces should be unique. If they are not unique, they must be of the same
type. The compiler will issue an error if the interfaces both declare a non-function member of the same
name, but of different types.

For function members, each function member of the same name is treated as describing an overload of the
same function. Of note, too, is that in the case of interface A merging with later interface A , the second
interface will have a higher precedence than the first.

That is, in the example:

interface Cloner {
clone(animal: Animal): Animal;
}

interface Cloner {
clone(animal: Sheep): Sheep;
}

interface Cloner {
clone(animal: Dog): Dog;
clone(animal: Cat): Cat;
}

The three interfaces will merge to create a single declaration as so:

interface Cloner {
clone(animal: Dog): Dog;
clone(animal: Cat): Cat;
clone(animal: Sheep): Sheep;
clone(animal: Animal): Animal;
}

Notice that the elements of each group maintains the same order, but the groups themselves are merged
with later overload sets ordered first.

One exception to this rule is specialized signatures. If a signature has a parameter whose type is a single
string literal type (e.g. not a union of string literals), then it will be bubbled toward the top of its merged
overload list.

For instance, the following interfaces will merge together:

interface Document {
createElement(tagName: any): Element;
}
interface Document {
createElement(tagName: "div"): HTMLDivElement;
createElement(tagName: "span"): HTMLSpanElement;
}
interface Document {

363
typescript

createElement(tagName: string): HTMLElement;


createElement(tagName: "canvas"): HTMLCanvasElement;
}

The resulting merged declaration of Document will be the following:

interface Document {
createElement(tagName: "canvas"): HTMLCanvasElement;
createElement(tagName: "div"): HTMLDivElement;
createElement(tagName: "span"): HTMLSpanElement;
createElement(tagName: string): HTMLElement;
createElement(tagName: any): Element;
}

Merging Namespaces
Similarly to interfaces, namespaces of the same name will also merge their members. Since namespaces
create both a namespace and a value, we need to understand how both merge.

To merge the namespaces, type definitions from exported interfaces declared in each namespace are them-
selves merged, forming a single namespace with merged interface definitions inside.

To merge the namespace value, at each declaration site, if a namespace already exists with the given name,
it is further extended by taking the existing namespace and adding the exported members of the second
namespace to the first.

The declaration merge of Animals in this example:

namespace Animals {
export class Zebra {}
}

namespace Animals {
export interface Legged {
numberOfLegs: number;
}
export class Dog {}
}

is equivalent to:

namespace Animals {
export interface Legged {
numberOfLegs: number;
}

export class Zebra {}


export class Dog {}
}

This model of namespace merging is a helpful starting place, but we also need to understand what happens
with non-exported members. Non-exported members are only visible in the original (un-merged) name-
space. This means that after merging, merged members that came from other declarations cannot see non-
exported members.

364
Declaration Merging

We can see this more clearly in this example:

namespace Animal {
let haveMuscles = true;

export function animalsHaveMuscles() {


return haveMuscles;
}
}

namespace Animal {
export function doAnimalsHaveMuscles() {
return haveMuscles; // Error, because haveMuscles is not accessible here
}
}

Because haveMuscles is not exported, only the animalsHaveMuscles function that shares the same un-
merged namespace can see the symbol. The doAnimalsHaveMuscles function, even though it's part of the
merged Animal namespace can not see this un-exported member.

Merging Namespaces with Classes, Functions, and


Enums
Namespaces are flexible enough to also merge with other types of declarations. To do so, the namespace
declaration must follow the declaration it will merge with. The resulting declaration has properties of both
declaration types. TypeScript uses this capability to model some of the patterns in JavaScript as well as oth-
er programming languages.

Merging Namespaces with Classes


This gives the user a way of describing inner classes.

class Album {
label: Album.AlbumLabel;
}
namespace Album {
export class AlbumLabel {}
}

The visibility rules for merged members is the same as described in the Merging Namespaces section, so we
must export the AlbumLabel class for the merged class to see it. The end result is a class managed inside
of another class. You can also use namespaces to add more static members to an existing class.

In addition to the pattern of inner classes, you may also be familiar with the JavaScript practice of creating
a function and then extending the function further by adding properties onto the function. TypeScript uses
declaration merging to build up definitions like this in a type-safe way.

function buildLabel(name: string): string {


return buildLabel.prefix + name + buildLabel.suffix;
}

namespace buildLabel {

365
typescript

export let suffix = "";


export let prefix = "Hello, ";
}

console.log(buildLabel("Sam Smith"));

Similarly, namespaces can be used to extend enums with static members:

enum Color {
red = 1,
green = 2,
blue = 4,
}

namespace Color {
export function mixColor(colorName: string) {
if (colorName == "yellow") {
return Color.red + Color.green;
} else if (colorName == "white") {
return Color.red + Color.green + Color.blue;
} else if (colorName == "magenta") {
return Color.red + Color.blue;
} else if (colorName == "cyan") {
return Color.green + Color.blue;
}
}
}

Disallowed Merges
Not all merges are allowed in TypeScript. Currently, classes can not merge with other classes or with vari-
ables. For information on mimicking class merging, see the Mixins in TypeScript section.

Module Augmentation
Although JavaScript modules do not support merging, you can patch existing objects by importing and then
updating them. Let's look at a toy Observable example:

// observable.ts
export class Observable<T> {
// ... implementation left as an exercise for the reader ...
}

// map.ts
import { Observable } from "./observable";
Observable.prototype.map = function (f) {
// ... another exercise for the reader
};

This works fine in TypeScript too, but the compiler doesn't know about Observable.prototype.map . You
can use module augmentation to tell the compiler about it:

// observable.ts
export class Observable<T> {
// ... implementation left as an exercise for the reader ...
}

366
Declaration Merging

// map.ts
import { Observable } from "./observable";
declare module "./observable" {
interface Observable<T> {
map<U>(f: (x: T) => U): Observable<U>;
}
}
Observable.prototype.map = function (f) {
// ... another exercise for the reader
};

// consumer.ts
import { Observable } from "./observable";
import "./map";
let o: Observable<number>;
o.map((x) => x.toFixed());

The module name is resolved the same way as module specifiers in import / export . See Modules for
more information. Then the declarations in an augmentation are merged as if they were declared in the
same file as the original.

However, there are two limitations to keep in mind:

1. You can't declare new top-level declarations in the augmentation -- just patches to existing declarations.
2. Default exports also cannot be augmented, only named exports (since you need to augment an export
by its exported name, and default is a reserved word - see #14080 for details)

Global augmentation
You can also add declarations to the global scope from inside a module:

// observable.ts
export class Observable<T> {
// ... still no implementation ...
}

declare global {
interface Array<T> {
toObservable(): Observable<T>;
}
}

Array.prototype.toObservable = function () {
// ...
};

Global augmentations have the same behavior and limits as module augmentations.

Go to TOC

367
typescript

Introduction

Further Reading:
A Complete Guide to TypeScript Decorators

With the introduction of Classes in TypeScript and ES6, there now exist certain scenarios that require addi-
tional features to support annotating or modifying classes and class members. Decorators provide a way to
add both annotations and a meta-programming syntax for class declarations and members. Decorators are
a stage 2 proposal for JavaScript and are available as an experimental feature of TypeScript.

NOTE Decorators are an experimental feature that may change in future releases.

To enable experimental support for decorators, you must enable the experimentalDecorators compiler
option either on the command line or in your tsconfig.json :

Command Line:

tsc --target ES5 --experimentalDecorators

tsconfig.json:

{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true
}
}

Decorators
A Decorator is a special kind of declaration that can be attached to a class declaration, method, accessor,
property, or parameter. Decorators use the form @expression , where expression must evaluate to a
function that will be called at runtime with information about the decorated declaration.

For example, given the decorator @sealed we might write the sealed function as follows:

function sealed(target) {
// do something with 'target' ...
}

368
Decorators

Decorator Factories
If we want to customize how a decorator is applied to a declaration, we can write a decorator factory. A
Decorator Factory is simply a function that returns the expression that will be called by the decorator at
runtime.

We can write a decorator factory in the following fashion:

function color(value: string) {


// this is the decorator factory, it sets up
// the returned decorator function
return function (target) {
// this is the decorator
// do something with 'target' and 'value'...
};
}

Decorator Composition
Multiple decorators can be applied to a declaration, for example on a single line:

// @experimentalDecorators
// @noErrors
function f() {}
function g() {}
// ---cut---
@f @g x

On multiple lines:

// @experimentalDecorators
// @noErrors
function f() {}
function g() {}
// ---cut---
@f
@g
x

When multiple decorators apply to a single declaration, their evaluation is similar to function composition in
mathematics. In this model, when composing functions f and g, the resulting composite (f ∘ g)(x) is equiva-
lent to f(g(x)).

As such, the following steps are performed when evaluating multiple decorators on a single declaration in
TypeScript:

1. The expressions for each decorator are evaluated top-to-bottom.


2. The results are then called as functions from bottom-to-top.

If we were to use decorator factories, we can observe this evaluation order with the following example:

369
typescript

// @experimentalDecorators
function first() {
console.log("first(): factory evaluated");
return function (target: any, propertyKey: string, descriptor:
PropertyDescriptor) {
console.log("first(): called");
};
}

function second() {
console.log("second(): factory evaluated");
return function (target: any, propertyKey: string, descriptor:
PropertyDescriptor) {
console.log("second(): called");
};
}

class ExampleClass {
@first()
@second()
method() {}
}

Which would print this output to the console:

first(): factory evaluated


second(): factory evaluated
second(): called
first(): called

Decorator Evaluation
There is a well defined order to how decorators applied to various declarations inside of a class are applied:

1. Parameter Decorators, followed by Method, Accessor, or Property Decorators are applied for each in-
stance member.
2. Parameter Decorators, followed by Method, Accessor, or Property Decorators are applied for each static
member.
3. Parameter Decorators are applied for the constructor.
4. Class Decorators are applied for the class.

Class Decorators
A Class Decorator is declared just before a class declaration. The class decorator is applied to the construc-
tor of the class and can be used to observe, modify, or replace a class definition. A class decorator cannot
be used in a declaration file, or in any other ambient context (such as on a declare class).

The expression for the class decorator will be called as a function at runtime, with the constructor of the
decorated class as its only argument.

If the class decorator returns a value, it will replace the class declaration with the provided constructor
function.

370
Decorators

NOTE Should you choose to return a new constructor function, you must take care to maintain the
original prototype. The logic that applies decorators at runtime will not do this for you.

The following is an example of a class decorator ( @sealed ) applied to a BugReport class:

// @experimentalDecorators
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
// ---cut---
@sealed
class BugReport {
type = "report";
title: string;

constructor(t: string) {
this.title = t;
}
}

We can define the @sealed decorator using the following function declaration:

function sealed(constructor: Function) {


Object.seal(constructor);
Object.seal(constructor.prototype);
}

When @sealed is executed, it will seal both the constructor and its prototype, and will therefore prevent
any further functionality from being added to or removed from this class during runtime by accessing
BugReport.prototype or by defining properties on BugReport itself (note that ES2015 classes are really
just syntactic sugar to prototype-based constructor functions). This decorator does not prevent classes
from sub-classing BugReport .

Next we have an example of how to override the constructor to set new defaults.

// @errors: 2339
// @experimentalDecorators
function reportableClassDecorator<T extends { new (...args: any[]): {} }>
(constructor: T) {
return class extends constructor {
reportingURL = "http://www...";
};
}

@reportableClassDecorator
class BugReport {
type = "report";
title: string;

constructor(t: string) {
this.title = t;
}
}

371
typescript

const bug = new BugReport("Needs dark mode");


console.log(bug.title); // Prints "Needs dark mode"
console.log(bug.type); // Prints "report"

// Note that the decorator _does not_ change the TypeScript type
// and so the new property `reportingURL` is not known
// to the type system:
bug.reportingURL;

Method Decorators
A Method Decorator is declared just before a method declaration. The decorator is applied to the Property
Descriptor for the method, and can be used to observe, modify, or replace a method definition. A method
decorator cannot be used in a declaration file, on an overload, or in any other ambient context (such as in a
declare class).

The expression for the method decorator will be called as a function at runtime, with the following three
arguments:

1. Either the constructor function of the class for a static member, or the prototype of the class for an in-
stance member.
2. The name of the member.
3. The Property Descriptor for the member.

NOTE The Property Descriptor will be undefined if your script target is less than ES5 .

If the method decorator returns a value, it will be used as the Property Descriptor for the method.

NOTE The return value is ignored if your script target is less than ES5 .

The following is an example of a method decorator ( @enumerable ) applied to a method on the Greeter
class:

// @experimentalDecorators
function enumerable(value: boolean) {
return function (target: any,propertyKey: string,descriptor: PropertyDescriptor)
{
descriptor.enumerable = value;
};
}
// ---cut---
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}

@enumerable(false)
greet() {

372
Decorators

return "Hello, " + this.greeting;


}
}

We can define the @enumerable decorator using the following function declaration:

function enumerable(value: boolean) {


return function (target: any, propertyKey: string, descriptor:
PropertyDescriptor) {
descriptor.enumerable = value;
};
}

The @enumerable(false) decorator here is a decorator factory. When the @enumerable(false) decorator
is called, it modifies the enumerable property of the property descriptor.

Accessor Decorators
An Accessor Decorator is declared just before an accessor declaration. The accessor decorator is applied to
the Property Descriptor for the accessor and can be used to observe, modify, or replace an accessor's defini-
tions. An accessor decorator cannot be used in a declaration file, or in any other ambient context (such as in
a declare class).

NOTE TypeScript disallows decorating both the get and set accessor for a single member.
Instead, all decorators for the member must be applied to the first accessor specified in document or-
der. This is because decorators apply to a Property Descriptor, which combines both the get and se
t accessor, not each declaration separately.

The expression for the accessor decorator will be called as a function at runtime, with the following three
arguments:

1. Either the constructor function of the class for a static member, or the prototype of the class for an in-
stance member.
2. The name of the member.
3. The Property Descriptor for the member.

NOTE The Property Descriptor will be undefined if your script target is less than ES5 .

If the accessor decorator returns a value, it will be used as the Property Descriptor for the member.

NOTE The return value is ignored if your script target is less than ES5 .

373
typescript

The following is an example of an accessor decorator ( @configurable ) applied to a member of the Point
class:

// @experimentalDecorators
function configurable(value: boolean) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
descriptor.configurable = value;
};
}
// ---cut---
class Point {
private _x: number;
private _y: number;
constructor(x: number, y: number) {
this._x = x;
this._y = y;
}

@configurable(false)
get x() {
return this._x;
}

@configurable(false)
get y() {
return this._y;
}
}

We can define the @configurable decorator using the following function declaration:

function configurable(value: boolean) {


return function (target: any, propertyKey: string, descriptor:
PropertyDescriptor) {
descriptor.configurable = value;
};
}

Property Decorators
A Property Decorator is declared just before a property declaration. A property decorator cannot be used in
a declaration file, or in any other ambient context (such as in a declare class).

The expression for the property decorator will be called as a function at runtime, with the following two
arguments:

1. Either the constructor function of the class for a static member, or the prototype of the class for an in-
stance member.
2. The name of the member.

374
Decorators

NOTE A Property Descriptor is not provided as an argument to a property decorator due to how
property decorators are initialized in TypeScript. This is because there is currently no mechanism to
describe an instance property when defining members of a prototype, and no way to observe or modi-
fy the initializer for a property. The return value is ignored too. As such, a property decorator can only
be used to observe that a property of a specific name has been declared for a class.

We can use this information to record metadata about the property, as in the following example:

class Greeter {
@format("Hello, %s")
greeting: string;

constructor(message: string) {
this.greeting = message;
}

greet() {
let formatString = getFormat(this, "greeting");
return formatString.replace("%s", this.greeting);
}
}

We can then define the @format decorator and getFormat functions using the following function
declarations:

import "reflect-metadata";

const formatMetadataKey = Symbol("format");

function format(formatString: string) {


return Reflect.metadata(formatMetadataKey, formatString);
}

function getFormat(target: any, propertyKey: string) {


return Reflect.getMetadata(formatMetadataKey, target, propertyKey);
}

The @format("Hello, %s") decorator here is a decorator factory. When @format("Hello, %s") is called,
it adds a metadata entry for the property using the Reflect.metadata function from the reflect-meta‐
data library. When getFormat is called, it reads the metadata value for the format.

NOTE This example requires the reflect-metadata library. See Metadata for more information
about the reflect-metadata library.

375
typescript

Parameter Decorators
A Parameter Decorator is declared just before a parameter declaration. The parameter decorator is applied
to the function for a class constructor or method declaration. A parameter decorator cannot be used in a de-
claration file, an overload, or in any other ambient context (such as in a declare class).

The expression for the parameter decorator will be called as a function at runtime, with the following three
arguments:

1. Either the constructor function of the class for a static member, or the prototype of the class for an in-
stance member.
2. The name of the member.
3. The ordinal index of the parameter in the function's parameter list.

NOTE A parameter decorator can only be used to observe that a parameter has been declared on a
method.

The return value of the parameter decorator is ignored.

The following is an example of a parameter decorator ( @required ) applied to parameter of a member of


the BugReport class:

// @experimentalDecorators
function validate(target: any, propertyName: string, descriptor:
TypedPropertyDescriptor<any>) {}
function required(target: Object, propertyKey: string | symbol, parameterIndex:
number) {}
// ---cut---
class BugReport {
type = "report";
title: string;

constructor(t: string) {
this.title = t;
}

@validate
print(@required verbose: boolean) {
if (verbose) {
return `type: ${this.type}\ntitle: ${this.title}`;
} else {
return this.title;
}
}
}

We can then define the @required and @validate decorators using the following function declarations:

// @experimentalDecorators
// @emitDecoratorMetadata
import "reflect-metadata";
const requiredMetadataKey = Symbol("required");

376
Decorators

function required(target: Object, propertyKey: string | symbol, parameterIndex:


number) {
let existingRequiredParameters: number[] =
Reflect.getOwnMetadata(requiredMetadataKey, target, propertyKey) || [];
existingRequiredParameters.push(parameterIndex);
Reflect.defineMetadata( requiredMetadataKey, existingRequiredParameters, target,
propertyKey);
}

function validate(target: any, propertyName: string, descriptor:


TypedPropertyDescriptor<Function>) {
let method = descriptor.value!;

descriptor.value = function () {
let requiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey,
target, propertyName);
if (requiredParameters) {
for (let parameterIndex of requiredParameters) {
if (parameterIndex >= arguments.length || arguments[parameterIndex] ===
undefined) {
throw new Error("Missing required argument.");
}
}
}
return method.apply(this, arguments);
};
}

The @required decorator adds a metadata entry that marks the parameter as required. The @validate
decorator then wraps the existing greet method in a function that validates the arguments before invoking
the original method.

NOTE This example requires the reflect-metadata library. See Metadata for more information
about the reflect-metadata library.

Metadata
Some examples use the reflect-metadata library which adds a polyfill for an experimental metadata API.
This library is not yet part of the ECMAScript (JavaScript) standard. However, once decorators are officially
adopted as part of the ECMAScript standard these extensions will be proposed for adoption.

You can install this library via npm:

npm i reflect-metadata --save

TypeScript includes experimental support for emitting certain types of metadata for declarations that have
decorators. To enable this experimental support, you must set the emitDecoratorMetadata compiler op-
tion either on the command line or in your tsconfig.json :

Command Line:

tsc --target ES5 --experimentalDecorators --emitDecoratorMetadata

377
typescript

tsconfig.json:

{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}

When enabled, as long as the reflect-metadata library has been imported, additional design-time type
information will be exposed at runtime.

We can see this in action in the following example:

// @emitDecoratorMetadata
// @experimentalDecorators
// @strictPropertyInitialization: false
import "reflect-metadata";

class Point {
constructor(public x: number, public y: number) {}
}

class Line {
private _start: Point;
private _end: Point;

@validate
set start(value: Point) {
this._start = value;
}

get start() {
return this._start;
}

@validate
set end(value: Point) {
this._end = value;
}

get end() {
return this._end;
}
}

function validate<T>(target: any, propertyKey: string, descriptor:


TypedPropertyDescriptor<T>) {
let set = descriptor.set!;

descriptor.set = function (value: T) {


let type = Reflect.getMetadata("design:type", target, propertyKey);

if (!(value instanceof type)) {


throw new TypeError(`Invalid type, got ${typeof value} not ${type.name}.`);
}

set.call(this, value);
};

378
Decorators

const line = new Line()


line.start = new Point(0, 0)

// @ts-ignore
// line.end = {}

// Fails at runtime with:


// > Invalid type, got object not Point

The TypeScript compiler will inject design-time type information using the @Reflect.metadata decorator.
You could consider it the equivalent of the following TypeScript:

class Line {
private _start: Point;
private _end: Point;

@validate
@Reflect.metadata("design:type", Point)
set start(value: Point) {
this._start = value;
}
get start() {
return this._start;
}

@validate
@Reflect.metadata("design:type", Point)
set end(value: Point) {
this._end = value;
}
get end() {
return this._end;
}
}

NOTE Decorator metadata is an experimental feature and may introduce breaking changes in future
releases.

Go to TOC

379
typescript

For the last few years, Node.js has been working to support running ECMAScript modules (ESM). This has
been a very difficult feature to support, since the foundation of the Node.js ecosystem is built on a different
module system called CommonJS (CJS).

Interoperating between the two module systems brings large challenges, with many new features to juggle;
however, support for ESM in Node.js is now implemented in Node.js, and the dust has begun to settle.

That's why TypeScript brings two new module and moduleResolution settings: node16 and nodenext .

{
"compilerOptions": {
"module": "nodenext",
}
}

These new modes bring a few high-level features which we'll explore here.

type in package.json and New Extensions


Node.js supports a new setting in package.json called type . "type" can be set to either "module" or
"commonjs" .

{
"name": "my-package",
"type": "module",

"//": "...",
"dependencies": {
}
}

This setting controls whether .js files are interpreted as ES modules or CommonJS modules, and defaults
to CommonJS when not set. When a file is considered an ES module, a few different rules come into play
compared to CommonJS:

import / export statements and top-level await can be used


relative import paths need full extensions (e.g we have to write import "./foo.js" instead of import
"./foo" )
imports might resolve differently from dependencies in node_modules
certain global-like values like require() and __dirname cannot be used directly
CommonJS modules get imported under certain special rules

We'll come back to some of these.

To overlay the way TypeScript works in this system, .ts and .tsx files now work the same way. When
TypeScript finds a .ts , .tsx , .js , or .jsx file, it will walk up looking for a package.json to see
whether that file is an ES module, and use that to determine:

how to find other modules which that file imports


and how to transform that file if producing outputs

380
ECMAScript Modules in Node.js

When a .ts file is compiled as an ES module, ECMAScript import / export syntax is left alone in the .js
output; when it's compiled as a CommonJS module, it will produce the same output you get today under
module : commonjs .

This also means paths resolve differently between .ts files that are ES modules and ones that are CJS
modules. For example, let's say you have the following code today:

// ./foo.ts
export function helper() {
// ...
}

// ./bar.ts
import { helper } from "./foo"; // only works in CJS

helper();

This code works in CommonJS modules, but will fail in ES modules because relative import paths need to
use extensions. As a result, it will have to be rewritten to use the extension of the output of foo.ts - so
bar.ts will instead have to import from ./foo.js .

// ./bar.ts
import { helper } from "./foo.js"; // works in ESM & CJS

helper();

This might feel a bit cumbersome at first, but TypeScript tooling like auto-imports and path completion will
typically just do this for you.

One other thing to mention is the fact that this applies to .d.ts files too. When TypeScript finds a .d.ts
file in package, whether it is treated as an ESM or CommonJS file is based on the containing package.

New File Extensions


The type field in package.json is nice because it allows us to continue using the .ts and .js file ex-
tensions which can be convenient; however, you will occasionally need to write a file that differs from what
type specifies. You might also just prefer to always be explicit.

Node.js supports two extensions to help with this: .mjs and .cjs . .mjs files are always ES modules,
and .cjs files are always CommonJS modules, and there's no way to override these.

In turn, TypeScript supports two new source file extensions: .mts and .cts . When TypeScript emits these
to JavaScript files, it will emit them to .mjs and .cjs respectively.

Furthermore, TypeScript also supports two new declaration file extensions: .d.mts and .d.cts . When
TypeScript generates declaration files for .mts and .cts , their corresponding extensions will be .d.mts
and .d.cts .

Using these extensions is entirely optional, but will often be useful even if you choose not to use them as
part of your primary workflow.

381
typescript

CommonJS Interop
Node.js allows ES modules to import CommonJS modules as if they were ES modules with a default export.

// @module: nodenext
// @filename: helper.cts
export function helper() {
console.log("hello world!");
}

// @filename: index.mts
import foo from "./helper.cjs";

// prints "hello world!"


foo.helper();

In some cases, Node.js also synthesizes named exports from CommonJS modules, which can be more con-
venient. In these cases, ES modules can use a "namespace-style" import (i.e. import * as foo from
"..." ), or named imports (i.e. import { helper } from "..." ).

// @module: nodenext
// @filename: helper.cts
export function helper() {
console.log("hello world!");
}

// @filename: index.mts
import { helper } from "./helper.cjs";

// prints "hello world!"


helper();

There isn't always a way for TypeScript to know whether these named imports will be synthesized, but
TypeScript will err on being permissive and use some heuristics when importing from a file that is definitely
a CommonJS module.

One TypeScript-specific note about interop is the following syntax:

import foo = require("foo");

In a CommonJS module, this just boils down to a require() call, and in an ES module, this imports cre‐
ateRequire to achieve the same thing. This will make code less portable on runtimes like the browser
(which don't support require() ), but will often be useful for interoperability. In turn, you can write the
above example using this syntax as follows:

// @module: nodenext
// @filename: helper.cts
export function helper() {
console.log("hello world!");
}

// @filename: index.mts
import foo = require("./foo.cjs");

foo.helper()

382
ECMAScript Modules in Node.js

Finally, it's worth noting that the only way to import ESM files from a CJS module is using dynamic
import() calls. This can present challenges, but is the behavior in Node.js today.

You can read more about ESM/CommonJS interop in Node.js here.

package.json Exports, Imports, and Self-Referencing


Node.js supports a new field for defining entry points in package.json called "exports" . This field is a
more powerful alternative to defining "main" in package.json , and can control what parts of your pack-
age are exposed to consumers.

Here's an package.json that supports separate entry-points for CommonJS and ESM:

// package.json
{
"name": "my-package",
"type": "module",
"exports": {
".": {
// Entry-point for `import "my-package"` in ESM
"import": "./esm/index.js",

// Entry-point for `require("my-package") in CJS


"require": "./commonjs/index.cjs",
},
},

// CJS fall-back for older versions of Node.js


"main": "./commonjs/index.cjs",
}

There's a lot to this feature, which you can read more about on the Node.js documentation. Here we'll try to
focus on how TypeScript supports it.

With TypeScript's original Node support, it would look for a "main" field, and then look for declaration files
that corresponded to that entry. For example, if "main" pointed to ./lib/index.js , TypeScript would
look for a file called ./lib/index.d.ts . A package author could override this by specifying a separate field
called "types" (e.g. "types": "./types/index.d.ts" ).

The new support works similarly with import conditions. By default, TypeScript overlays the same rules with
import conditions - if you write an import from an ES module, it will look up the import field, and from a
CommonJS module, it will look at the require field. If it finds them, it will look for a colocated declaration
file. If you need to point to a different location for your type declarations, you can add a "types" import
condition.

// package.json
{
"name": "my-package",
"type": "module",
"exports": {
".": {
// Entry-point for TypeScript resolution - must occur first!
"types": "./types/index.d.ts",

383
typescript

// Entry-point for `import "my-package"` in ESM


"import": "./esm/index.js",

// Entry-point for `require("my-package") in CJS


"require": "./commonjs/index.cjs",
},
},

// CJS fall-back for older versions of Node.js


"main": "./commonjs/index.cjs",

// Fall-back for older versions of TypeScript


"types": "./types/index.d.ts"
}

TypeScript also supports the "imports" field of package.json in a similar manner (looking for declaration
files alongside corresponding files), and supports packages self-referencing themselves. These features are
generally not as involved, but are supported.

Go to TOC

384
Enums

Enums are one of the few features TypeScript has which is not a type-level extension of JavaScript.

Enums allow a developer to define a set of named constants. Using enums can make it easier to document
intent, or create a set of distinct cases. TypeScript provides both numeric and string-based enums.

Numeric enums
We'll first start off with numeric enums, which are probably more familiar if you're coming from other lan-
guages. An enum can be defined using the enum keyword.

enum Direction {
Up = 1,
Down,
Left,
Right,
}

Above, we have a numeric enum where Up is initialized with 1 . All of the following members are auto-in-
cremented from that point on. In other words, Direction.Up has the value 1 , Down has 2 , Left has
3 , and Right has 4 .

If we wanted, we could leave off the initializers entirely:

enum Direction {
Up,
Down,
Left,
Right,
}

Here, Up would have the value 0 , Down would have 1 , etc. This auto-incrementing behavior is useful for
cases where we might not care about the member values themselves, but do care that each value is distinct
from other values in the same enum.

Using an enum is simple: just access any member as a property off of the enum itself, and declare types
using the name of the enum:

enum UserResponse {
No = 0,
Yes = 1,
}

function respond(recipient: string, message: UserResponse): void {


// ...
}

respond("Princess Caroline", UserResponse.Yes);

Numeric enums can be mixed in computed and constant members (see below). The short story is, enums
without initializers either need to be first, or have to come after numeric enums initialized with numeric con-
stants or other constant enum members. In other words, the following isn't allowed:

385
typescript

// @errors: 1061
const getSomeValue = () => 23;
// ---cut---
enum E {
A = getSomeValue(),
B,
}

String enums
String enums are a similar concept, but have some subtle runtime differences as documented below. In a
string enum, each member has to be constant-initialized with a string literal, or with another string enum
member.

enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}

While string enums don't have auto-incrementing behavior, string enums have the benefit that they "serial-
ize" well. In other words, if you were debugging and had to read the runtime value of a numeric enum, the
value is often opaque - it doesn't convey any useful meaning on its own (though reverse mapping can often
help). String enums allow you to give a meaningful and readable value when your code runs, independent
of the name of the enum member itself.

Heterogeneous enums
Technically enums can be mixed with string and numeric members, but it's not clear why you would ever
want to do so:

enum BooleanLikeHeterogeneousEnum {
No = 0,
Yes = "YES",
}

Unless you're really trying to take advantage of JavaScript's runtime behavior in a clever way, it's advised
that you don't do this.

Computed and constant members


Each enum member has a value associated with it which can be either constant or computed. An enum
member is considered constant if:

It is the first member in the enum and it has no initializer, in which case it's assigned the value 0 :

// E.X is constant:
enum E {
X,
}

386
Enums

It does not have an initializer and the preceding enum member was a numeric constant. In this case the
value of the current enum member will be the value of the preceding enum member plus one.

// All enum members in 'E1' and 'E2' are constant.

enum E1 {
X,
Y,
Z,
}

enum E2 {
A = 1,
B,
C,
}

The enum member is initialized with a constant enum expression. A constant enum expression is a sub-
set of TypeScript expressions that can be fully evaluated at compile time. An expression is a constant
enum expression if it is:

1. a literal enum expression (basically a string literal or a numeric literal)


2. a reference to previously defined constant enum member (which can originate from a different enum)
3. a parenthesized constant enum expression
4. one of the + , - , ~ unary operators applied to constant enum expression
5. + , - , * , / , % , << , >> , >>> , & , | , ^ binary operators with constant enum expressions as
operands

It is a compile time error for constant enum expressions to be evaluated to NaN or Infinity .

In all other cases enum member is considered computed.

enum FileAccess {
// constant members
None,
Read = 1 << 1,
Write = 1 << 2,
ReadWrite = Read | Write,
// computed member
G = "123".length,
}

Union enums and enum member types


There is a special subset of constant enum members that aren't calculated: literal enum members. A literal
enum member is a constant enum member with no initialized value, or with values that are initialized to

any string literal (e.g. "foo" , "bar , "baz" )


any numeric literal (e.g. 1 , 100 )
a unary minus applied to any numeric literal (e.g. -1 , -100 )

When all members in an enum have literal enum values, some special semantics come into play.

387
typescript

The first is that enum members also become types as well! For example, we can say that certain members
can only have the value of an enum member:

// @errors: 2322
enum ShapeKind {
Circle,
Square,
}

interface Circle {
kind: ShapeKind.Circle;
radius: number;
}

interface Square {
kind: ShapeKind.Square;
sideLength: number;
}

let c: Circle = {
kind: ShapeKind.Square,
radius: 100,
};

The other change is that enum types themselves effectively become a union of each enum member. With
union enums, the type system is able to leverage the fact that it knows the exact set of values that exist in
the enum itself. Because of that, TypeScript can catch bugs where we might be comparing values
incorrectly. For example:

// @errors: 2367
enum E {
Foo,
Bar,
}

function f(x: E) {
if (x !== E.Foo || x !== E.Bar) {
//
}
}

In that example, we first checked whether x was not E.Foo . If that check succeeds, then our || will
short-circuit, and the body of the 'if' will run. However, if the check didn't succeed, then x can only be
E.Foo , so it doesn't make sense to see whether it's equal to E.Bar .

Enums at runtime
Enums are real objects that exist at runtime. For example, the following enum

enum E {
X,
Y,
Z,
}

388
Enums

can actually be passed around to functions

enum E {
X,
Y,
Z,
}

function f(obj: { X: number }) {


return obj.X;
}

// Works, since 'E' has a property named 'X' which is a number.


f(E);

Enums at compile time


Even though Enums are real objects that exist at runtime, the keyof keyword works differently than you
might expect for typical objects. Instead, use keyof typeof to get a Type that represents all Enum keys
as strings.

enum LogLevel {
ERROR,
WARN,
INFO,
DEBUG,
}

/**
* This is equivalent to:
* type LogLevelStrings = 'ERROR' | 'WARN' | 'INFO' | 'DEBUG';
*/
type LogLevelStrings = keyof typeof LogLevel;

function printImportant(key: LogLevelStrings, message: string) {


const num = LogLevel[key];
if (num <= LogLevel.WARN) {
console.log("Log level key is:", key);
console.log("Log level value is:", num);
console.log("Log level message is:", message);
}
}
printImportant("ERROR", "This is a message");

Reverse mappings
In addition to creating an object with property names for members, numeric enums members also get a re-
verse mapping from enum values to enum names. For example, in this example:

enum Enum {
A,
}

let a = Enum.A;
let nameOfA = Enum[a]; // "A"

389
typescript

TypeScript compiles this down to the following JavaScript:

// @showEmit
enum Enum {
A,
}

let a = Enum.A;
let nameOfA = Enum[a]; // "A"

In this generated code, an enum is compiled into an object that stores both forward ( name -> value ) and
reverse ( value -> name ) mappings. References to other enum members are always emitted as property
accesses and never inlined.

Keep in mind that string enum members do not get a reverse mapping generated at all.

const enums
In most cases, enums are a perfectly valid solution. However sometimes requirements are tighter. To avoid
paying the cost of extra generated code and additional indirection when accessing enum values, it's possible
to use const enums. Const enums are defined using the const modifier on our enums:

const enum Enum {


A = 1,
B = A * 2,
}

Const enums can only use constant enum expressions and unlike regular enums they are completely re-
moved during compilation. Const enum members are inlined at use sites. This is possible since const enums
cannot have computed members.

const enum Direction {


Up,
Down,
Left,
Right,
}

let directions = [
Direction.Up,
Direction.Down,
Direction.Left,
Direction.Right,
];

in generated code will become

// @showEmit
const enum Direction {
Up,
Down,
Left,
Right,
}

390
Enums

let directions = [
Direction.Up,
Direction.Down,
Direction.Left,
Direction.Right,
];

Const enum pitfalls

Inlining enum values is straightforward at first, but comes with subtle implications. These pitfalls pertain to
ambient const enums only (basically const enums in .d.ts files) and sharing them between projects, but if
you are publishing or consuming .d.ts files, these pitfalls likely apply to you, because tsc --declara‐
tion transforms .ts files into .d.ts files.

1. For the reasons laid out in the isolatedModules documentation, that mode is fundamentally incompati-
ble with ambient const enums. This means if you publish ambient const enums, downstream consumers
will not be able to use isolatedModules and those enum values at the same time.
2. You can easily inline values from version A of a dependency at compile time, and import version B at
runtime. Version A and B's enums can have different values, if you are not very careful, resulting in sur-
prising bugs, like taking the wrong branches of if statements. These bugs are especially pernicious be-
cause it is common to run automated tests at roughly the same time as projects are built, with the same
dependency versions, which misses these bugs completely.
3. importsNotUsedAsValues: "preserve" will not elide imports for const enums used as values, but am-
bient const enums do not guarantee that runtime .js files exist. The unresolvable imports cause errors
at runtime. The usual way to unambiguously elide imports, type-only imports, does not allow const enum
values, currently.

Here are two approaches to avoiding these pitfalls:

A. Do not use const enums at all. You can easily ban const enums with the help of a linter. Obviously this
avoids any issues with const enums, but prevents your project from inlining its own enums. Unlike inlining
enums from other projects, inlining a project's own enums is not problematic and has performance implica-
tions. B. Do not publish ambient const enums, by deconstifying them with the help of preserveConst‐
Enums . This is the approach taken internally by the TypeScript project itself. preserveConstEnums emits
the same JavaScript for const enums as plain enums. You can then safely strip the const modifier from
.d.ts files in a build step.

This way downstream consumers will not inline enums from your project, avoiding the pitfalls above, but a
project can still inline its own enums, unlike banning const enums entirely.

Ambient enums
Ambient enums are used to describe the shape of already existing enum types.

declare enum Enum {


A = 1,
B,
C = 2,
}

391
typescript

One important difference between ambient and non-ambient enums is that, in regular enums, members
that don't have an initializer will be considered constant if its preceding enum member is considered con-
stant. By contrast, an ambient (and non-const) enum member that does not have an initializer is always
considered computed.

Objects vs Enums
In modern TypeScript, you may not need an enum when an object with as const could suffice:

const enum EDirection {


Up,
Down,
Left,
Right,
}

const ODirection = {
Up: 0,
Down: 1,
Left: 2,
Right: 3,
} as const;

EDirection.Up;
// ^?

ODirection.Up;
// ^?

// Using the enum as a parameter


function walk(dir: EDirection) {}

// It requires an extra line to pull out the values


type Direction = typeof ODirection[keyof typeof ODirection];
function run(dir: Direction) {}

walk(EDirection.Left);
run(ODirection.Right);

The biggest argument in favour of this format over TypeScript's enum is that it keeps your codebase aligned
with the state of JavaScript, and when/if enums are added to JavaScript then you can move to the addition-
al syntax.

Go to TOC

392
Iterators and Generators

Iterables
An object is deemed iterable if it has an implementation for the Symbol.iterator property. Some built-in
types like Array , Map , Set , String , Int32Array , Uint32Array , etc. have their Symbol.iterator
property already implemented. Symbol.iterator function on an object is responsible for returning the list
of values to iterate on.

Iterable interface
Iterable is a type we can use if we want to take in types listed above which are iterable. Here is an
example:

function toArray<X>(xs: Iterable<X>): X[] {


return [...xs]
}

for..of statements
for..of loops over an iterable object, invoking the Symbol.iterator property on the object. Here is a
simple for..of loop on an array:

let someArray = [1, "string", false];

for (let entry of someArray) {


console.log(entry); // 1, "string", false
}

for..of vs. for..in statements


Both for..of and for..in statements iterate over lists; the values iterated on are different though,
for..in returns a list of keys on the object being iterated, whereas for..of returns a list of values of
the numeric properties of the object being iterated.

Here is an example that demonstrates this distinction:

let list = [4, 5, 6];

for (let i in list) {


console.log(i); // "0", "1", "2",
}

for (let i of list) {


console.log(i); // 4, 5, 6
}

Another distinction is that for..in operates on any object; it serves as a way to inspect properties on this
object. for..of on the other hand, is mainly interested in values of iterable objects. Built-in objects like
Map and Set implement Symbol.iterator property allowing access to stored values.

393
typescript

let pets = new Set(["Cat", "Dog", "Hamster"]);


pets["species"] = "mammals";

for (let pet in pets) {


console.log(pet); // "species"
}

for (let pet of pets) {


console.log(pet); // "Cat", "Dog", "Hamster"
}

Code generation
Targeting ES5 and ES3

When targeting an ES5 or ES3-compliant engine, iterators are only allowed on values of Array type. It is
an error to use for..of loops on non-Array values, even if these non-Array values implement the
Symbol.iterator property.

The compiler will generate a simple for loop for a for..of loop, for instance:

let numbers = [1, 2, 3];


for (let num of numbers) {
console.log(num);
}

will be generated as:

var numbers = [1, 2, 3];


for (var _i = 0; _i < numbers.length; _i++) {
var num = numbers[_i];
console.log(num);
}

Targeting ECMAScript 2015 and higher

When targeting an ECMAScipt 2015-compliant engine, the compiler will generate for..of loops to target
the built-in iterator implementation in the engine.

Go to TOC

394
JSX

JSX is an embeddable XML-like syntax. It is meant to be transformed into valid JavaScript, though the se-
mantics of that transformation are implementation-specific. JSX rose to popularity with the React
framework, but has since seen other implementations as well. TypeScript supports embedding, type check-
ing, and compiling JSX directly to JavaScript.

Basic usage
In order to use JSX you must do two things.

1. Name your files with a .tsx extension


2. Enable the jsx option

TypeScript ships with three JSX modes: preserve , react , and react-native . These modes only affect
the emit stage - type checking is unaffected. The preserve mode will keep the JSX as part of the output to
be further consumed by another transform step (e.g. Babel). Additionally the output will have a .jsx file
extension. The react mode will emit React.createElement , does not need to go through a JSX transfor-
mation before use, and the output will have a .js file extension. The react-native mode is the equiva-
lent of preserve in that it keeps all JSX, but the output will instead have a .js file extension.

Output File
Mode Input Output
Extension

<div
preserve <div /> .jsx
/>

<div
react React.createElement("div") .js
/>

react- <div
<div /> .js
native />

<div
react-jsx _jsx("div", {}, void 0); .js
/>

react- <div _jsxDEV("div", {}, void 0, false, {...},


.js
jsxdev /> this);

You can specify this mode using either the jsx command line flag or the corresponding option jsx in your
tsconfig.json file.

*Note: You can specify the JSX factory function to use when targeting react JSX emit with jsxFactor
y option (defaults to React.createElement )

395
typescript

The as operator
Recall how to write a type assertion:

const foo = <foo>bar;

This asserts the variable bar to have the type foo . Since TypeScript also uses angle brackets for type as-
sertions, combining it with JSX's syntax would introduce certain parsing difficulties. As a result, TypeScript
disallows angle bracket type assertions in .tsx files.

Since the above syntax cannot be used in .tsx files, an alternate type assertion operator should be used:
as . The example can easily be rewritten with the as operator.

const foo = bar as foo;

The as operator is available in both .ts and .tsx files, and is identical in behavior to the angle-bracket
type assertion style.

Type Checking
In order to understand type checking with JSX, you must first understand the difference between intrinsic
elements and value-based elements. Given a JSX expression <expr /> , expr may either refer to some-
thing intrinsic to the environment (e.g. a div or span in a DOM environment) or to a custom component
that you've created. This is important for two reasons:

1. For React, intrinsic elements are emitted as strings ( React.createElement("div") ), whereas a compo-
nent you've created is not ( React.createElement(MyComponent) ).
2. The types of the attributes being passed in the JSX element should be looked up differently. Intrinsic ele-
ment attributes should be known intrinsically whereas components will likely want to specify their own
set of attributes.

TypeScript uses the same convention that React does for distinguishing between these. An intrinsic element
always begins with a lowercase letter, and a value-based element always begins with an uppercase letter.

Intrinsic elements
Intrinsic elements are looked up on the special interface JSX.IntrinsicElements . By default, if this inter-
face is not specified, then anything goes and intrinsic elements will not be type checked. However, if this in-
terface is present, then the name of the intrinsic element is looked up as a property on the
JSX.IntrinsicElements interface. For example:

declare namespace JSX {


interface IntrinsicElements {
foo: any;
}
}

396
JSX

<foo />; // ok
<bar />; // error

In the above example, <foo /> will work fine but <bar /> will result in an error since it has not been
specified on JSX.IntrinsicElements .

Note: You can also specify a catch-all string indexer on JSX.IntrinsicElements as follows:

declare namespace JSX {


interface IntrinsicElements {
[elemName: string]: any;
}
}

Value-based elements
Value-based elements are simply looked up by identifiers that are in scope.

import MyComponent from "./myComponent";

<MyComponent />; // ok
<SomeOtherComponent />; // error

There are two ways to define a value-based element:

1. Function Component (FC)


2. Class Component

Because these two types of value-based elements are indistinguishable from each other in a JSX
expression, first TS tries to resolve the expression as a Function Component using overload resolution. If
the process succeeds, then TS finishes resolving the expression to its declaration. If the value fails to re-
solve as a Function Component, TS will then try to resolve it as a class component. If that fails, TS will re-
port an error.

Function Component
As the name suggests, the component is defined as a JavaScript function where its first argument is a
props object. TS enforces that its return type must be assignable to JSX.Element .

interface FooProp {
name: string;
X: number;
Y: number;
}

declare function AnotherComponent(prop: { name: string });


function ComponentFoo(prop: FooProp) {
return <AnotherComponent name={prop.name} />;
}

397
typescript

const Button = (prop: { value: string }, context: { color: string }) => (


<button />
);

Because a Function Component is simply a JavaScript function, function overloads may be used here as
well:

// @noErrors
declare module JSX {
interface Element {}
interface IntrinsicElements {
[s: string]: any;
}
}
// ---cut---
interface ClickableProps {
children: JSX.Element[] | JSX.Element;
}

interface HomeProps extends ClickableProps {


home: JSX.Element;
}

interface SideProps extends ClickableProps {


side: JSX.Element | string;
}

function MainButton(prop: HomeProps): JSX.Element;


function MainButton(prop: SideProps): JSX.Element;
function MainButton(prop: ClickableProps): JSX.Element {
// ...
}

Note: Function Components were formerly known as Stateless Function Components (SFC). As
Function Components can no longer be considered stateless in recent versions of react, the type SFC
and its alias StatelessComponent were deprecated.

Class Component
It is possible to define the type of a class component. However, to do so it is best to understand two new
terms: the element class type and the element instance type.

Given <Expr /> , the element class type is the type of Expr . So in the example above, if MyComponent
was an ES6 class the class type would be that class's constructor and statics. If MyComponent was a factory
function, the class type would be that function.

Once the class type is established, the instance type is determined by the union of the return types of the
class type's construct or call signatures (whichever is present). So again, in the case of an ES6 class, the in-
stance type would be the type of an instance of that class, and in the case of a factory function, it would be
the type of the value returned from the function.

398
JSX

class MyComponent {
render() {}
}

// use a construct signature


const myComponent = new MyComponent();

// element class type => MyComponent


// element instance type => { render: () => void }

function MyFactoryFunction() {
return {
render: () => {},
};
}

// use a call signature


const myComponent = MyFactoryFunction();

// element class type => MyFactoryFunction


// element instance type => { render: () => void }

The element instance type is interesting because it must be assignable to JSX.ElementClass or it will re-
sult in an error. By default JSX.ElementClass is {} , but it can be augmented to limit the use of JSX to
only those types that conform to the proper interface.

declare namespace JSX {


interface ElementClass {
render: any;
}
}

class MyComponent {
render() {}
}
function MyFactoryFunction() {
return { render: () => {} };
}

<MyComponent />; // ok
<MyFactoryFunction />; // ok

class NotAValidComponent {}
function NotAValidFactoryFunction() {
return {};
}

<NotAValidComponent />; // error


<NotAValidFactoryFunction />; // error

Attribute type checking


The first step to type checking attributes is to determine the element attributes type. This is slightly differ-
ent between intrinsic and value-based elements.

For intrinsic elements, it is the type of the property on JSX.IntrinsicElements

399
typescript

declare namespace JSX {


interface IntrinsicElements {
foo: { bar?: boolean };
}
}

// element attributes type for 'foo' is '{bar?: boolean}'


<foo bar />;

For value-based elements, it is a bit more complex. It is determined by the type of a property on the ele-
ment instance type that was previously determined. Which property to use is determined by
JSX.ElementAttributesProperty . It should be declared with a single property. The name of that property
is then used. As of TypeScript 2.8, if JSX.ElementAttributesProperty is not provided, the type of first
parameter of the class element's constructor or Function Component's call will be used instead.

declare namespace JSX {


interface ElementAttributesProperty {
props; // specify the property name to use
}
}

class MyComponent {
// specify the property on the element instance type
props: {
foo?: string;
};
}

// element attributes type for 'MyComponent' is '{foo?: string}'


<MyComponent foo="bar" />;

The element attribute type is used to type check the attributes in the JSX. Optional and required properties
are supported.

declare namespace JSX {


interface IntrinsicElements {
foo: { requiredProp: string; optionalProp?: number };
}
}

<foo requiredProp="bar" />; // ok


<foo requiredProp="bar" optionalProp={0} />; // ok
<foo />; // error, requiredProp is missing
<foo requiredProp={0} />; // error, requiredProp should be a string
<foo requiredProp="bar" unknownProp />; // error, unknownProp does not exist
<foo requiredProp="bar" some-unknown-prop />; // ok, because 'some-unknown-prop'
is not a valid identifier

Note: If an attribute name is not a valid JS identifier (like a data-* attribute), it is not considered to
be an error if it is not found in the element attributes type.

400
JSX

Additionally, the JSX.IntrinsicAttributes interface can be used to specify extra properties used by the
JSX framework which are not generally used by the components' props or arguments - for instance key in
React. Specializing further, the generic JSX.IntrinsicClassAttributes<T> type may also be used to
specify the same kind of extra attributes just for class components (and not Function Components). In this
type, the generic parameter corresponds to the class instance type. In React, this is used to allow the ref
attribute of type Ref<T> . Generally speaking, all of the properties on these interfaces should be optional,
unless you intend that users of your JSX framework need to provide some attribute on every tag.

The spread operator also works:

const props = { requiredProp: "bar" };


<foo {...props} />; // ok

const badProps = {};


<foo {...badProps} />; // error

Children Type Checking


In TypeScript 2.3, TS introduced type checking of children. children is a special property in an element at-
tributes type where child JSXExpressions are taken to be inserted into the attributes. Similar to how TS uses
JSX.ElementAttributesProperty to determine the name of props, TS uses
JSX.ElementChildrenAttribute to determine the name of children within those props.
JSX.ElementChildrenAttribute should be declared with a single property.

declare namespace JSX {


interface ElementChildrenAttribute {
children: {}; // specify children name to use
}
}

<div>
<h1>Hello</h1>
</div>;

<div>
<h1>Hello</h1>
World
</div>;

const CustomComp = (props) => <div>{props.children}</div>


<CustomComp>
<div>Hello World</div>
{"This is just a JS expression..." + 1000}
</CustomComp>

You can specify the type of children like any other attribute. This will override the default type from, e.g. the
React typings if you use them.

interface PropsType {
children: JSX.Element
name: string
}

401
typescript

class Component extends React.Component<PropsType, {}> {


render() {
return (
<h2>
{this.props.children}
</h2>
)
}
}

// OK
<Component name="foo">
<h1>Hello World</h1>
</Component>

// Error: children is of type JSX.Element not array of JSX.Element


<Component name="bar">
<h1>Hello World</h1>
<h2>Hello World</h2>
</Component>

// Error: children is of type JSX.Element not array of JSX.Element or string.


<Component name="baz">
<h1>Hello</h1>
World
</Component>

The JSX result type


By default the result of a JSX expression is typed as any . You can customize the type by specifying the
JSX.Element interface. However, it is not possible to retrieve type information about the element, attribut-
es or children of the JSX from this interface. It is a black box.

Embedding Expressions
JSX allows you to embed expressions between tags by surrounding the expressions with curly braces ( {
} ).

const a = (
<div>
{["foo", "bar"].map((i) => (
<span>{i / 2}</span>
))}
</div>
);

The above code will result in an error since you cannot divide a string by a number. The output, when using
the preserve option, looks like:

const a = (
<div>
{["foo", "bar"].map(function (i) {
return <span>{i / 2}</span>;
})}
</div>
);

402
JSX

React integration
To use JSX with React you should use the React typings. These typings define the JSX namespace appro-
priately for use with React.

/// <reference path="react.d.ts" />

interface Props {
foo: string;
}

class MyComponent extends React.Component<Props, {}> {


render() {
return <span>{this.props.foo}</span>;
}
}

<MyComponent foo="bar" />; // ok


<MyComponent foo={0} />; // error

Configuring JSX
There are multiple compiler flags which can be used to customize your JSX, which work as both a compiler
flag and via inline per-file pragmas. To learn more see their tsconfig reference pages:

jsxFactory
jsxFragmentFactory
jsxImportSource

Go to TOC

403
typescript

Along with traditional OO hierarchies, another popular way of building up classes from reusable components
is to build them by combining simpler partial classes. You may be familiar with the idea of mixins or traits
for languages like Scala, and the pattern has also reached some popularity in the JavaScript community.

How Does A Mixin Work?


The pattern relies on using generics with class inheritance to extend a base class. TypeScript's best mixin
support is done via the class expression pattern. You can read more about how this pattern works in
JavaScript here.

To get started, we'll need a class which will have the mixins applied on top of:

class Sprite {
name = "";
x = 0;
y = 0;

constructor(name: string) {
this.name = name;
}
}

Then you need a type and a factory function which returns a class expression extending the base class.

// To get started, we need a type which we'll use to extend


// other classes from. The main responsibility is to declare
// that the type being passed in is a class.

type Constructor = new (...args: any[]) => {};

// This mixin adds a scale property, with getters and setters


// for changing it with an encapsulated private property:

function Scale<TBase extends Constructor>(Base: TBase) {


return class Scaling extends Base {
// Mixins may not declare private/protected properties
// however, you can use ES2020 private fields
_scale = 1;

setScale(scale: number) {
this._scale = scale;
}

get scale(): number {


return this._scale;
}
};
}

With these all set up, then you can create a class which represents the base class with mixins applied:

class Sprite {
name = "";
x = 0;
y = 0;

404
Mixins

constructor(name: string) {
this.name = name;
}
}
type Constructor = new (...args: any[]) => {};
function Scale<TBase extends Constructor>(Base: TBase) {
return class Scaling extends Base {
// Mixins may not declare private/protected properties
// however, you can use ES2020 private fields
_scale = 1;

setScale(scale: number) {
this._scale = scale;
}

get scale(): number {


return this._scale;
}
};
}
// ---cut---
// Compose a new class from the Sprite class,
// with the Mixin Scale applier:
const EightBitSprite = Scale(Sprite);

const flappySprite = new EightBitSprite("Bird");


flappySprite.setScale(0.8);
console.log(flappySprite.scale);

Constrained Mixins
In the above form, the mixin's have no underlying knowledge of the class which can make it hard to create
the design you want.

To model this, we modify the original constructor type to accept a generic argument.

// This was our previous constructor:


type Constructor = new (...args: any[]) => {};
// Now we use a generic version which can apply a constraint on
// the class which this mixin is applied to
type GConstructor<T = {}> = new (...args: any[]) => T;

This allows for creating classes which only work with constrained base classes:

type GConstructor<T = {}> = new (...args: any[]) => T;


class Sprite {
name = "";
x = 0;
y = 0;

constructor(name: string) {
this.name = name;
}
}
// ---cut---
type Positionable = GConstructor<{ setPos: (x: number, y: number) => void }>;
type Spritable = GConstructor<Sprite>;
type Loggable = GConstructor<{ print: () => void }>;

405
typescript

Then you can create mixins which only work when you have a particular base to build on:

type GConstructor<T = {}> = new (...args: any[]) => T;


class Sprite {
name = "";
x = 0;
y = 0;

constructor(name: string) {
this.name = name;
}
}
type Positionable = GConstructor<{ setPos: (x: number, y: number) => void }>;
type Spritable = GConstructor<Sprite>;
type Loggable = GConstructor<{ print: () => void }>;
// ---cut---

function Jumpable<TBase extends Positionable>(Base: TBase) {


return class Jumpable extends Base {
jump() {
// This mixin will only work if it is passed a base
// class which has setPos defined because of the
// Positionable constraint.
this.setPos(0, 20);
}
};
}

Alternative Pattern
Previous versions of this document recommended a way to write mixins where you created both the runtime
and type hierarchies separately, then merged them at the end:

// @strict: false
// Each mixin is a traditional ES class
class Jumpable {
jump() {}
}

class Duckable {
duck() {}
}

// Including the base


class Sprite {
x = 0;
y = 0;
}

// Then you create an interface which merges


// the expected mixins with the same name as your base
interface Sprite extends Jumpable, Duckable {}
// Apply the mixins into the base class via
// the JS at runtime
applyMixins(Sprite, [Jumpable, Duckable]);

let player = new Sprite();


player.jump();
console.log(player.x, player.y);

406
Mixins

// This can live anywhere in your codebase:


function applyMixins(derivedCtor: any, constructors: any[]) {
constructors.forEach((baseCtor) => {
Object.getOwnPropertyNames(baseCtor.prototype).forEach((name) => {
Object.defineProperty(
derivedCtor.prototype,
name,
Object.getOwnPropertyDescriptor(baseCtor.prototype, name) ||
Object.create(null)
);
});
});
}

This pattern relies less on the compiler, and more on your codebase to ensure both runtime and type-sys-
tem are correctly kept in sync.

Constraints
The mixin pattern is supported natively inside the TypeScript compiler by code flow analysis. There are a
few cases where you can hit the edges of the native support.

Decorators and Mixins #4881

You cannot use decorators to provide mixins via code flow analysis:

// @experimentalDecorators
// @errors: 2339
// A decorator function which replicates the mixin pattern:
const Pausable = (target: typeof Player) => {
return class Pausable extends target {
shouldFreeze = false;
};
};

@Pausable
class Player {
x = 0;
y = 0;
}

// The Player class does not have the decorator's type merged:
const player = new Player();
player.shouldFreeze;

// The runtime aspect could be manually replicated via


// type composition or interface merging.
type FreezablePlayer = Player & { shouldFreeze: boolean };

const playerTwo = (new Player() as unknown) as FreezablePlayer;


playerTwo.shouldFreeze;

Static Property Mixins #17829

More of a gotcha than a constraint. The class expression pattern creates singletons, so they can't be
mapped at the type system to support different variable types.

407
typescript

You can work around this by using functions to return your classes which differ based on a generic:

function base<T>() {
class Base {
static prop: T;
}
return Base;
}

function derived<T>() {
class Derived extends base<T>() {
static anotherProp: T;
}
return Derived;
}

class Spec extends derived<string>() {}

Spec.prop; // string
Spec.anotherProp; // string

Go to TOC

408
Module Resolution

This section assumes some basic knowledge about modules. Please see the Modules documentation
for more information.

Module resolution is the process the compiler uses to figure out what an import refers to. Consider an im-
port statement like import { a } from "moduleA" ; in order to check any use of a , the compiler needs
to know exactly what it represents, and will need to check its definition moduleA .

At this point, the compiler will ask "what's the shape of moduleA ?" While this sounds straightforward, mod‐
uleA could be defined in one of your own .ts / .tsx files, or in a .d.ts that your code depends on.

First, the compiler will try to locate a file that represents the imported module. To do so the compiler follows
one of two different strategies: Classic or Node. These strategies tell the compiler where to look for mod‐
uleA .

If that didn't work and if the module name is non-relative (and in the case of "moduleA" , it is), then the
compiler will attempt to locate an ambient module declaration. We'll cover non-relative imports next.

Finally, if the compiler could not resolve the module, it will log an error. In this case, the error would be
something like error TS2307: Cannot find module 'moduleA'.

Relative vs. Non-relative module imports


Module imports are resolved differently based on whether the module reference is relative or non-relative.

A relative import is one that starts with / , ./ or ../ . Some examples include:

import Entry from "./components/Entry";


import { DefaultHeaders } from "../constants/http";
import "/mod";

Any other import is considered non-relative. Some examples include:

import * as $ from "jquery";


import { Component } from "@angular/core";

A relative import is resolved relative to the importing file and cannot resolve to an ambient module declara-
tion. You should use relative imports for your own modules that are guaranteed to maintain their relative lo-
cation at runtime.

A non-relative import can be resolved relative to baseUrl , or through path mapping, which we'll cover be-
low. They can also resolve to ambient module declarations. Use non-relative paths when importing any of
your external dependencies.

409
typescript

Module Resolution Strategies


There are two possible module resolution strategies: Node and Classic. You can use the moduleResolution
option to specify the module resolution strategy. If not specified, the default is Node for --module common‐
js , and Classic otherwise (including when module is set to amd , system , umd , es2015 , esnext , etc.).

Note: node module resolution is the most-commonly used in the TypeScript community and is rec-
ommended for most projects. If you are having resolution problems with import s and export s in
TypeScript, try setting moduleResolution: "node" to see if it fixes the issue.

Classic
This used to be TypeScript's default resolution strategy. Nowadays, this strategy is mainly present for back-
ward compatibility.

A relative import will be resolved relative to the importing file. So import { b } from "./moduleB" in
source file /root/src/folder/A.ts would result in the following lookups:

1. /root/src/folder/moduleB.ts
2. /root/src/folder/moduleB.d.ts

For non-relative module imports, however, the compiler walks up the directory tree starting with the direc-
tory containing the importing file, trying to locate a matching definition file.

For example:

A non-relative import to moduleB such as import { b } from "moduleB" , in a source file


/root/src/folder/A.ts , would result in attempting the following locations for locating "moduleB" :

1. /root/src/folder/moduleB.ts
2. /root/src/folder/moduleB.d.ts
3. /root/src/moduleB.ts
4. /root/src/moduleB.d.ts
5. /root/moduleB.ts
6. /root/moduleB.d.ts
7. /moduleB.ts
8. /moduleB.d.ts

Node
This resolution strategy attempts to mimic the Node.js module resolution mechanism at runtime. The full
Node.js resolution algorithm is outlined in Node.js module documentation.

410
Module Resolution

How Node.js resolves modules

To understand what steps the TS compiler will follow, it is important to shed some light on Node.js modules.
Traditionally, imports in Node.js are performed by calling a function named require . The behavior Node.js
takes will differ depending on if require is given a relative path or a non-relative path.

Relative paths are fairly straightforward. As an example, let's consider a file located at /root/src/module‐
A.js , which contains the import var x = require("./moduleB"); Node.js resolves that import in the fol-
lowing order:

1. Ask the file named /root/src/moduleB.js , if it exists.

2. Ask the folder /root/src/moduleB if it contains a file named package.json that specifies a "main"
module. In our example, if Node.js found the file /root/src/moduleB/package.json containing {
"main": "lib/mainModule.js" } , then Node.js will refer to /root/src/moduleB/lib/mainModule.js .

3. Ask the folder /root/src/moduleB if it contains a file named index.js . That file is implicitly consid-
ered that folder's "main" module.

You can read more about this in Node.js documentation on file modules and folder modules.

However, resolution for a non-relative module name is performed differently. Node will look for your mod-
ules in special folders named node_modules . A node_modules folder can be on the same level as the cur-
rent file, or higher up in the directory chain. Node will walk up the directory chain, looking through each
node_modules until it finds the module you tried to load.

Following up our example above, consider if /root/src/moduleA.js instead used a non-relative path and
had the import var x = require("moduleB"); . Node would then try to resolve moduleB to each of the
locations until one worked.

1. /root/src/node_modules/moduleB.js
2. /root/src/node_modules/moduleB/package.json (if it specifies a "main" property)
3. /root/src/node_modules/moduleB/index.js

4. /root/node_modules/moduleB.js
5. /root/node_modules/moduleB/package.json (if it specifies a "main" property)
6. /root/node_modules/moduleB/index.js

7. /node_modules/moduleB.js
8. /node_modules/moduleB/package.json (if it specifies a "main" property)
9. /node_modules/moduleB/index.js

Notice that Node.js jumped up a directory in steps (4) and (7).

You can read more about the process in Node.js documentation on loading modules from node_modules .

411
typescript

How TypeScript resolves modules

TypeScript will mimic the Node.js run-time resolution strategy in order to locate definition files for modules
at compile-time. To accomplish this, TypeScript overlays the TypeScript source file extensions ( .ts , .tsx ,
and .d.ts ) over Node's resolution logic. TypeScript will also use a field in package.json named types
to mirror the purpose of "main" - the compiler will use it to find the "main" definition file to consult.

For example, an import statement like import { b } from "./moduleB" in /root/src/moduleA.ts


would result in attempting the following locations for locating "./moduleB" :

1. /root/src/moduleB.ts
2. /root/src/moduleB.tsx
3. /root/src/moduleB.d.ts
4. /root/src/moduleB/package.json (if it specifies a types property)
5. /root/src/moduleB/index.ts
6. /root/src/moduleB/index.tsx
7. /root/src/moduleB/index.d.ts

Recall that Node.js looked for a file named moduleB.js , then an applicable package.json , and then for
an index.js .

Similarly, a non-relative import will follow the Node.js resolution logic, first looking up a file, then looking up
an applicable folder. So import { b } from "moduleB" in source file /root/src/moduleA.ts would re-
sult in the following lookups:

1. /root/src/node_modules/moduleB.ts
2. /root/src/node_modules/moduleB.tsx
3. /root/src/node_modules/moduleB.d.ts
4. /root/src/node_modules/moduleB/package.json (if it specifies a types property)
5. /root/src/node_modules/@types/moduleB.d.ts
6. /root/src/node_modules/moduleB/index.ts
7. /root/src/node_modules/moduleB/index.tsx
8. /root/src/node_modules/moduleB/index.d.ts

9. /root/node_modules/moduleB.ts
10. /root/node_modules/moduleB.tsx
11. /root/node_modules/moduleB.d.ts
12. /root/node_modules/moduleB/package.json (if it specifies a types property)
13. /root/node_modules/@types/moduleB.d.ts
14. /root/node_modules/moduleB/index.ts
15. /root/node_modules/moduleB/index.tsx
16. /root/node_modules/moduleB/index.d.ts

17. /node_modules/moduleB.ts
18. /node_modules/moduleB.tsx

412
Module Resolution

19. /node_modules/moduleB.d.ts
20. /node_modules/moduleB/package.json (if it specifies a types property)
21. /node_modules/@types/moduleB.d.ts
22. /node_modules/moduleB/index.ts
23. /node_modules/moduleB/index.tsx
24. /node_modules/moduleB/index.d.ts

Don't be intimidated by the number of steps here - TypeScript is still only jumping up directories twice at
steps (9) and (17). This is really no more complex than what Node.js itself is doing.

Additional module resolution flags


A project source layout sometimes does not match that of the output. Usually a set of build steps result in
generating the final output. These include compiling .ts files into .js , and copying dependencies from
different source locations to a single output location. The net result is that modules at runtime may have
different names than the source files containing their definitions. Or module paths in the final output may
not match their corresponding source file paths at compile time.

The TypeScript compiler has a set of additional flags to inform the compiler of transformations that are ex-
pected to happen to the sources to generate the final output.

It is important to note that the compiler will not perform any of these transformations; it just uses these
pieces of information to guide the process of resolving a module import to its definition file.

Base URL
Using a baseUrl is a common practice in applications using AMD module loaders where modules are "de-
ployed" to a single folder at run-time. The sources of these modules can live in different directories, but a
build script will put them all together.

Setting baseUrl informs the compiler where to find modules. All module imports with non-relative names
are assumed to be relative to the baseUrl .

Value of baseUrl is determined as either:

value of baseUrl command line argument (if given path is relative, it is computed based on current
directory)
value of baseUrl property in 'tsconfig.json' (if given path is relative, it is computed based on the location
of 'tsconfig.json')

Note that relative module imports are not impacted by setting the baseUrl, as they are always resolved rel-
ative to their importing files.

You can find more documentation on baseUrl in RequireJS and SystemJS documentation.

413
typescript

Path mapping
Sometimes modules are not directly located under baseUrl. For instance, an import to a module "jquery"
would be translated at runtime to "node_modules/jquery/dist/jquery.slim.min.js" . Loaders use a
mapping configuration to map module names to files at run-time, see RequireJs documentation and
SystemJS documentation.

The TypeScript compiler supports the declaration of such mappings using paths property in tsconfig.j‐
son files. Here is an example for how to specify the paths property for jquery .

{
"compilerOptions": {
"baseUrl": ".", // This must be specified if "paths" is.
"paths": {
"jquery": ["node_modules/jquery/dist/jquery"] // This mapping is relative to
"baseUrl"
}
}
}

Please notice that paths are resolved relative to baseUrl . When setting baseUrl to a value other than
"." , i.e. the directory of tsconfig.json , the mappings must be changed accordingly. Say, you set
"baseUrl": "./src" in the above example, then jquery should be mapped to
"../node_modules/jquery/dist/jquery" .

Using paths also allows for more sophisticated mappings including multiple fall back locations. Consider a
project configuration where only some modules are available in one location, and the rest are in another. A
build step would put them all together in one place. The project layout may look like:

projectRoot
├── folder1
│ ├── file1.ts (imports 'folder1/file2' and 'folder2/file3')
│ └── file2.ts
├── generated
│ ├── folder1
│ └── folder2
│ └── file3.ts
└── tsconfig.json

The corresponding tsconfig.json would look like:

{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"*": ["*", "generated/*"]
}
}
}

This tells the compiler for any module import that matches the pattern "*" (i.e. all values), to look in two
locations:

414
Module Resolution

1. "*" : meaning the same name unchanged, so map <moduleName> => <baseUrl>/<moduleName>
2. "generated/*" meaning the module name with an appended prefix "generated", so map
<moduleName> => <baseUrl>/generated/<moduleName>

Following this logic, the compiler will attempt to resolve the two imports as such:

import 'folder1/file2':

1. pattern '*' is matched and wildcard captures the whole module name
2. try first substitution in the list: '*' -> folder1/file2
3. result of substitution is non-relative name - combine it with baseUrl ->
projectRoot/folder1/file2.ts .
4. File exists. Done.

import 'folder2/file3':

1. pattern '*' is matched and wildcard captures the whole module name
2. try first substitution in the list: '*' -> folder2/file3
3. result of substitution is non-relative name - combine it with baseUrl ->
projectRoot/folder2/file3.ts .
4. File does not exist, move to the second substitution
5. second substitution 'generated/*' -> generated/folder2/file3
6. result of substitution is non-relative name - combine it with baseUrl -> projectRoot/generated/fold‐
er2/file3.ts .
7. File exists. Done.

Virtual Directories with rootDirs


Sometimes the project sources from multiple directories at compile time are all combined to generate a sin-
gle output directory. This can be viewed as a set of source directories create a "virtual" directory.

Using rootDirs , you can inform the compiler of the roots making up this "virtual" directory; and thus the
compiler can resolve relative modules imports within these "virtual" directories as if they were merged to-
gether in one directory.

For example consider this project structure:

src
└── views
└── view1.ts (imports './template1')
└── view2.ts

generated
└── templates
└── views
└── template1.ts (imports './view2')

415
typescript

Files in src/views are user code for some UI controls. Files in generated/templates are UI template
binding code auto-generated by a template generator as part of the build. A build step will copy the files in
/src/views and /generated/templates/views to the same directory in the output. At run-time, a view
can expect its template to exist next to it, and thus should import it using a relative name as
"./template" .

To specify this relationship to the compiler, use rootDirs . rootDirs specify a list of roots whose contents
are expected to merge at run-time. So following our example, the tsconfig.json file should look like:

{
"compilerOptions": {
"rootDirs": ["src/views", "generated/templates/views"]
}
}

Every time the compiler sees a relative module import in a subfolder of one of the rootDirs , it will at-
tempt to look for this import in each of the entries of rootDirs .

The flexibility of rootDirs is not limited to specifying a list of physical source directories that are logically
merged. The supplied array may include any number of ad hoc, arbitrary directory names, regardless of
whether they exist or not. This allows the compiler to capture sophisticated bundling and runtime features
such as conditional inclusion and project specific loader plugins in a type safe way.

Consider an internationalization scenario where a build tool automatically generates locale specific bundles
by interpolating a special path token, say #{locale} , as part of a relative module path such as ./#{lo‐
cale}/messages . In this hypothetical setup the tool enumerates supported locales, mapping the abstracted
path into ./zh/messages , ./de/messages , and so forth.

Assume that each of these modules exports an array of strings. For example ./zh/messages might
contain:

export default [" 您好吗", "很高兴认识你"];


By leveraging rootDirs we can inform the compiler of this mapping and thereby allow it to safely resolve
./#{locale}/messages , even though the directory will never exist. For example, with the following
tsconfig.json :

{
"compilerOptions": {
"rootDirs": ["src/zh", "src/de", "src/#{locale}"]
}
}

The compiler will now resolve import messages from './#{locale}/messages' to import messages
from './zh/messages' for tooling purposes, allowing development in a locale agnostic manner without
compromising design time support.

416
Module Resolution

Tracing module resolution


As discussed earlier, the compiler can visit files outside the current folder when resolving a module. This can
be hard when diagnosing why a module is not resolved, or is resolved to an incorrect definition. Enabling
the compiler module resolution tracing using traceResolution provides insight in what happened during
the module resolution process.

Let's say we have a sample application that uses the typescript module. app.ts has an import like im‐
port * as ts from "typescript" .

│ tsconfig.json
├───node_modules
│ └───typescript
│ └───lib
│ typescript.d.ts
└───src
app.ts

Invoking the compiler with traceResolution

tsc --traceResolution

Results in an output such as:

======== Resolving module 'typescript' from 'src/app.ts'. ========


Module resolution kind is not specified, using 'NodeJs'.
Loading module 'typescript' from 'node_modules' folder.
File 'src/node_modules/typescript.ts' does not exist.
File 'src/node_modules/typescript.tsx' does not exist.
File 'src/node_modules/typescript.d.ts' does not exist.
File 'src/node_modules/typescript/package.json' does not exist.
File 'node_modules/typescript.ts' does not exist.
File 'node_modules/typescript.tsx' does not exist.
File 'node_modules/typescript.d.ts' does not exist.
Found 'package.json' at 'node_modules/typescript/package.json'.
'package.json' has 'types' field './lib/typescript.d.ts' that references
'node_modules/typescript/lib/typescript.d.ts'.
File 'node_modules/typescript/lib/typescript.d.ts' exist - use it as a module
resolution result.
======== Module name 'typescript' was successfully resolved to
'node_modules/typescript/lib/typescript.d.ts'. ========

Things to look out for

Name and location of the import

======== Resolving module 'typescript' from 'src/app.ts'. ========

The strategy the compiler is following

417
typescript

Module resolution kind is not specified, using 'NodeJs'.

Loading of types from npm packages

'package.json' has 'types' field './lib/typescript.d.ts' that references


'node_modules/typescript/lib/typescript.d.ts'.

Final result

======== Module name 'typescript' was successfully resolved to


'node_modules/typescript/lib/typescript.d.ts'. ========

Using --noResolve
Normally the compiler will attempt to resolve all module imports before it starts the compilation process.
Every time it successfully resolves an import to a file, the file is added to the set of files the compiler will
process later on.

The noResolve compiler options instructs the compiler not to "add" any files to the compilation that were
not passed on the command line. It will still try to resolve the module to files, but if the file is not specified,
it will not be included.

For instance:

app.ts

import * as A from "moduleA"; // OK, 'moduleA' passed on the command-line


import * as B from "moduleB"; // Error TS2307: Cannot find module 'moduleB'.

tsc app.ts moduleA.ts --noResolve

Compiling app.ts using noResolve should result in:

Correctly finding moduleA as it was passed on the command-line.


Error for not finding moduleB as it was not passed.

418
Module Resolution

Common Questions
Why does a module in the exclude list still get picked up by the
compiler?
tsconfig.json turns a folder into a “project”. Without specifying any “exclude” or “files” entries, all
files in the folder containing the tsconfig.json and all its sub-directories are included in your compilation.
If you want to exclude some of the files use “exclude” , if you would rather specify all the files instead of
letting the compiler look them up, use “files” .

That was tsconfig.json automatic inclusion. That does not embed module resolution as discussed above.
If the compiler identified a file as a target of a module import, it will be included in the compilation regard-
less if it was excluded in the previous steps.

So to exclude a file from the compilation, you need to exclude it and all files that have an import or ///
<reference path="..." /> directive to it.

Go to TOC

419
typescript

Starting with ECMAScript 2015, JavaScript has a concept of modules. TypeScript shares this concept.

Modules are executed within their own scope, not in the global scope; this means that variables, functions,
classes, etc. declared in a module are not visible outside the module unless they are explicitly exported us-
ing one of the export forms. Conversely, to consume a variable, function, class, interface, etc. exported
from a different module, it has to be imported using one of the import forms.

Modules are declarative; the relationships between modules are specified in terms of imports and exports at
the file level.

Modules import one another using a module loader. At runtime the module loader is responsible for locating
and executing all dependencies of a module before executing it. Well-known module loaders used in
JavaScript are Node.js's loader for CommonJS modules and the RequireJS loader for AMD modules in Web
applications.

In TypeScript, just as in ECMAScript 2015, any file containing a top-level import or export is considered
a module. Conversely, a file without any top-level import or export declarations is treated as a script
whose contents are available in the global scope (and therefore to modules as well).

Export
Exporting a declaration
Any declaration (such as a variable, function, class, type alias, or interface) can be exported by adding the
export keyword.

StringValidator.ts

export interface StringValidator {


isAcceptable(s: string): boolean;
}

ZipCodeValidator.ts

import { StringValidator } from "./StringValidator";

export const numberRegexp = /^[0-9]+$/;

export class ZipCodeValidator implements StringValidator {


isAcceptable(s: string) {
return s.length === 5 && numberRegexp.test(s);
}
}

Export statements
Export statements are handy when exports need to be renamed for consumers, so the above example can
be written as:

420
Modules

class ZipCodeValidator implements StringValidator {


isAcceptable(s: string) {
return s.length === 5 && numberRegexp.test(s);
}
}
export { ZipCodeValidator };
export { ZipCodeValidator as mainValidator };

Re-exports
Often modules extend other modules, and partially expose some of their features. A re-export does not im-
port it locally, or introduce a local variable.

ParseIntBasedZipCodeValidator.ts

export class ParseIntBasedZipCodeValidator {


isAcceptable(s: string) {
return s.length === 5 && parseInt(s).toString() === s;
}
}

// Export original validator but rename it


export { ZipCodeValidator as RegExpBasedZipCodeValidator } from
"./ZipCodeValidator";

Optionally, a module can wrap one or more modules and combine all their exports using export * from
"module" syntax.

AllValidators.ts

export * from "./StringValidator"; // exports 'StringValidator' interface


export * from "./ZipCodeValidator"; // exports 'ZipCodeValidator' class and
'numberRegexp' constant value
export * from "./ParseIntBasedZipCodeValidator"; // exports the
'ParseIntBasedZipCodeValidator' class
// and re-exports 'RegExpBasedZipCodeValidator' as alias
// of the 'ZipCodeValidator' class from 'ZipCodeValidator.ts'
// module.

Import
Importing is just about as easy as exporting from a module. Importing an exported declaration is done
through using one of the import forms below:

Import a single export from a module


import { ZipCodeValidator } from "./ZipCodeValidator";

let myValidator = new ZipCodeValidator();

imports can also be renamed

import { ZipCodeValidator as ZCV } from "./ZipCodeValidator";


let myValidator = new ZCV();

421
typescript

Import the entire module into a single variable, and


use it to access the module exports
import * as validator from "./ZipCodeValidator";
let myValidator = new validator.ZipCodeValidator();

Import a module for side-effects only


Though not recommended practice, some modules set up some global state that can be used