Command Palette

Search for a command to run...

Hectal
PHASE 3Beginner ~22 min· topic 1 of 6

Topic 3.1

Type Aliases

In one line

A type alias gives a name to any type with type Name = ..., so you can write a long or important type once and reuse it. The name is only a label: it does not create a new, different type.

Think of it like this

A contact in your phone. Instead of dialling "+91 98765 43210" every time, you save it as "Mum". The name is a shortcut to the same number, and if the number changes you fix it in one place. A type alias is a saved contact for a type. You write the long description once, give it a name, and use the name everywhere.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Type alias
A name you give to a type with type Name = .... It is a label, not a new kind of value.
Annotation
The : Type you write after a variable or parameter to say what it holds.
Object type
A type written with braces, like { x: number; y: number }, that lists the properties a value must have.
Union
A choice between types written with |, such as string | number. Topic 4.1 covers it in full.
Literal type
A type that allows exactly one value, like "open" or 42. Topic 4.2 covers it.
Tuple
An array with a fixed length where each position has its own type, like [string, number]. Topic 5.2 covers it.
Type parameter
A blank in a type, written in angle brackets like <T>, that the user of the type fills in.
Erased
Removed before the program runs. All types, including aliases, are erased, so they cost nothing at runtime.
PascalCase
A naming style where every word starts with a capital letter and there are no spaces: UserId, HttpMethod.

Step by step

01Save a type under a name

Write type, a name, =, then the type. From then on the name stands for the whole type.

Here Point is the object shape and UserId is a primitive. Both are used as ordinary annotations afterwards.

aliases.tswhole filets
type UserId = string;
type Point = { x: number; y: number };

const id: UserId = "u-42";
const origin: Point = { x: 0, y: 0 };

02Name a choice

Aliases are not only for objects. A union of exact texts becomes a tiny "menu" of allowed values, which is how many programs replace magic strings.

Putting any other text into a Status variable is an error, and the editor even suggests the two allowed values while you type.

status.tswhole filets
type Status = "open" | "closed";

let status: Status = "open";
status = "closed";  // fine
status = "paused";  // error TS2322: Type '"paused"' is not assignable to type 'Status'.

03An alias is not a new type

Two aliases of the same type are interchangeable. Meters and Feet are both number, so assigning one to the other is allowed even though mixing metres and feet is a real-world bug.

Use aliases to make intent visible to humans. If you need the checker to enforce the difference, you need more than an alias.

same-type.tswhole filets
type Meters = number;
type Feet = number;

const height: Meters = 2;
const converted: Feet = height; // allowed: both are just number

04Reuse and parameters

A function type is awkward to repeat, so it earns a name. A type parameter lets one alias work for many types: Pair<T> is a tuple of two Ts, Maybe<T> is a T or null.

Writing Pair<number> fills the blank: it means [number, number].

reuse.tswhole filets
type Callback = (error: Error | null, data?: string) => void;
type Pair<T> = [first: T, second: T];
type Maybe<T> = T | null;

const nickname: Maybe<string> = null;
const swap = <T>(p: Pair<T>): Pair<T> => [p[1], p[0]];

05Aliases live only in the checker

When TypeScript removes types, every alias goes with them. The JavaScript has no trace of Point or Status.

That is why an alias is free at runtime, and why you cannot use an alias name where a value is expected.

Aliases live only in the checkerdiagram
Rendering diagram…

Try it yourself

  1. 1

    Add a third status

    In the first example, change the alias to type Status = "open" | "closed" | "paused";. Predict what happens to the red error before you look.

    The error disappears: "paused" is now one of the allowed texts. You edited one line and every use of Status followed.

  2. 2

    Make Feet a string

    In the second example, change type Feet = number; to type Feet = string;. Predict which lines turn red.

    Now Meters and Feet really differ, so four errors appear. Line 5, const converted: Feet = height;, puts a number into a string. Line 9, in toFeet, returns a number where a string was promised. On line 12 the call toFeet(converted) passes a string where a number is expected, and .toFixed does not exist on a string. One edit to the alias reached every line that used it.

  3. 3

    Fill the blank

    In the third example add const names: Pair<string> = ["a", "b", "c"];. Read the error: which length does the alias demand?

    It says Source has 3 element(s) but target allows only 2. Pair is a tuple of exactly two, so a third element is refused.

Code & diagrams

Name a type, use it everywhere

Notice the Types panel: the object shape and the union keep their alias names, but UserId is shown as plain string. The last line is the error. The code still runs, so the console shows what happened before it.

Console

(nothing was printed)

Expected console output

u-42 closed 5
Two names, one type

No error: a Meters value goes into a Feet variable because both are number. The hover shows number, not Feet. Aliases document; they do not fence.

Console

(nothing was printed)

Expected console output

6.56
true
Function aliases, type parameters and unions

The arrow function passed to load has no annotations: TypeScript takes err and data from the Callback alias. Hover over them in your editor to see.

Console

(nothing was printed)

Expected console output

null ready
[ 2, 1 ] null

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Make two aliases really different

In the second example change type Meters = number; to type Meters = string;.

terminal
$ Types panel
── what you'll see ──
index.ts(4,7): error TS2322: Type 'number' is not assignable to type 'string'.
index.ts(5,7): error TS2322: Type 'string' is not assignable to type 'number'.
index.ts(9,10): error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.
index.ts(12,20): error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.

Break #2

Use a type as a value

At the very end of the first example, add the line const make = Point;.

terminal
$ Types panel and Console
── what you'll see ──
index.ts(19,14): error TS2693: 'Point' only refers to a type, but is being used as a value here.
Uncaught ReferenceError: Point is not defined

Myth vs fact

Myth

A type alias creates a new, separate type.

Fact

It only creates another name for the same type. Two aliases of number are fully interchangeable.

Myth

Aliases are only for object shapes.

Fact

Any type can be aliased: primitives, unions, functions, tuples, arrays, even other aliases with type parameters.

Myth

Aliases cost something at runtime.

Fact

They are erased completely. The compiled JavaScript contains no trace of them.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    When TypeScript prints a type, it keeps an alias name if the type came from one in a way it can track (object shapes, unions, instantiated generics) and drops it for primitives. This is why hover text is sometimes Point and sometimes a long expansion. It is display only; assignability never depends on the name.

  • ▸

    Aliases can be recursive when the recursion goes through an object property, array or tuple: type Tree = { value: number; children: Tree[] }. A direct self-reference like type Bad = Bad | string is an error (TS2456: Type alias 'Bad' circularly references itself). Recursive conditional types were added in TypeScript 4.1.

  • ▸

    Do not alias everything. A name earns its place when it carries meaning (UserId), when the type is long, or when it is shared. type Name = string used once in one function only makes readers jump to its definition.

Remember this

  1. 1

    The problem this solves: a type such as (error: Error | null, data?: string) => void is long, and writing it in ten places means ten places to fix when it changes. The syntax is type Name = SomeType;. After it, Name can be used anywhere a type goes. type UserId = string; makes UserId mean string. type Point = { x: number; y: number }; makes Point mean "an object with a number x and a number y". type Status = "open" | "closed"; names a choice between two exact texts. The word on the right can be any type: a primitive, an object shape, a union, a function, a tuple, an array.

  2. 2

    An alias is just a name, not a new type. type Meters = number; and type Feet = number; are both number, so TypeScript happily lets you put a Meters value into a Feet variable (the second example shows this). Aliases make code easier to read (UserId says more than string) but do not protect you from mixing things up. Real protection takes a trick called branding (Topic 10.4).

  3. 3

    Because an alias disappears when the checker looks inside, hover text often shows the alias name for object shapes and unions (Point, Status) but the underlying type for plain primitives (const id: string, not const id: UserId). Nothing is wrong: for a primitive there is nothing to keep, so the name is dropped.

  4. 4

    Aliases shine when a type is long or used in many places. Compare (error: Error | null, data?: string) => void repeated in ten functions with Callback. If the shape changes you edit one line, and the compiler then points at every place that no longer fits. They also give names to ideas: Status, Colour, Pair<string>.

  5. 5

    Aliases can take type parameters, the same idea as function parameters but for types: type Maybe<T> = T | null; and then Maybe<string> means string | null. You will study generics in depth in Phase 7; for now, read <T> as "a blank to fill in when you use it".

  6. 6

    Two limits. An alias exists only for the checker: it is erased from the JavaScript, so you can never use Point as a value (const x = Point is an error, see Break it). And an alias name must be unique in its scope: declaring type Config twice is an error (an interface behaves differently, Topic 3.4). Name aliases in PascalCase (UserId, HttpMethod) so they look different from variables.

Explain it without notes

01

What is a type alias, and what can it name?

02

If I write type Meters = number and type Feet = number, can I pass a Meters where a Feet is expected? Why?

03

Why can't you use a type alias as a value at runtime?

04

When is an alias worth creating?

Practice

01

Write a type alias Direction that allows only the texts "up", "down", "left" and "right", and a function turn(d: Direction): Direction that returns the opposite direction. Show what TypeScript says for turn("north").

02

Create an alias Pair<T> for two values of the same type and a function first that returns the first element of a Pair. Call it with numbers and with texts and write down the inferred return types.

Trade-offs

  • ↔

    Aliases improve readability and give one place to change a type, but they add a name readers must look up. Use them for shared or meaningful types, not for every annotation.

  • ↔

    Because an alias is only a label, it never prevents mixing two ideas that share a base type. Do not rely on type UserId = string to stop a ProductId slipping in.

Done when you can

  • I can write an alias for a primitive, an object shape, a union and a function type.

  • I can explain why Meters and Feet (both number) are interchangeable.

  • I can explain why an alias cannot be used as a runtime value.

  • I can write a generic alias like Maybe<T> and fill it in.

  • I can decide whether a type deserves a name.