Type Aliases and Literals
Naming types and encoding specific values in the type system with literal types
So far, we've used type annotations like number, string, and object
type literals. But TypeScript also lets you name types with the
type keyword, creating aliases that make your code more readable and
maintainable. Even more powerful: you can use literal types to
express that a value is not just any string or number, but a
specific string or number. This chapter shows you how to harness these
features to build self-documenting, precise type systems.
Type aliases: naming types
A type alias binds a name to a type. It's like a variable, but for types instead of values:
The alias Point is just another name for { x: number; y: number }.
Anywhere you see Point, you can mentally substitute the right-hand
side. But the name conveys intent better than a raw object type literal
repeated everywhere.
You can alias any type, not just objects:
What the compiler now knows: ID and Name are still just number
and string under the hood — TypeScript doesn't create a new runtime
type. But the alias documents what each value represents. If you
accidentally assign a Name to a variable expecting an ID, the
compiler won't stop you (they're both number), but the code will be
semantically confusing. For stronger separation, you'd use
discriminated unions (covered in Unions and
Intersections).
type vs interface revisited
We introduced interface in the previous
chapter. Both type and interface can
describe object shapes, but they differ in a few ways:
- Syntax for objects:
interfaceuses a declaration block;typeuses an assignment:interface Point { x: number; y: number } type Point = { x: number; y: number }; - Unions and intersections:
typecan express unions (A | B) and intersections (A & B).interfacecannot. - Declaration merging:
interfacesupports it;typedoes not. - Extension syntax:
interfaceusesextends;typeuses&.
Here's a comparison:
Both approaches produce the same shape. The choice is stylistic, but a
common convention is: use interface for public object APIs, use type
for unions, primitives, and internal utilities.
String literal types
A string literal type is a type that holds exactly one string
value. For example, the type "red" contains only the string "red":
By itself, a single literal type is not very useful. But when you combine several with a union, you get a lightweight enumeration:
What bugs does this prevent? If you have a function that expects a
primary color, you can't accidentally pass "yellow" or "purple". The
compiler catches the mistake.
This is far superior to using a plain string, where any typo would slip
through unnoticed.
Number and boolean literal types
You can also have number literals and boolean literals:
Number literals are useful for modeling fixed sets of codes (HTTP status codes, error codes) or configuration flags.
Literal narrowing with const
When you declare a variable with const, TypeScript infers the literal
type, not the general primitive:
This is because const variables can't be reassigned, so the compiler
knows the value will always be "Hello". With let, the variable can
be reassigned, so the compiler widens the type to string.
Why does this matter? Literal types enable more precise inference in certain patterns, like discriminated unions and function overloads. The narrower the type, the more the compiler can prove about your code.
The as const assertion
What if you want literal types for the elements of an object or array,
not just top-level const bindings? Use the as const assertion:
The as const assertion does two things:
- It narrows each property to its literal type (
"production"instead ofstring,3000instead ofnumber). - It makes all properties
readonly, so you can't accidentally mutate them.
This is especially useful for configuration objects or lookup tables where the values are fixed at compile time:
The typeof STATUS_CODES[keyof typeof STATUS_CODES] idiom extracts a
union of all the values in the object. This is a common pattern for
creating enums from objects.
Literal types as lightweight enums
TypeScript has a dedicated enum keyword, but many developers prefer
union of string literals as a lighter-weight alternative:
The literal union is simpler: no runtime code, no namespace pollution. The
enum keyword generates a JavaScript object at runtime, which can be
useful for reverse lookups (Color[Color.Red] === "Red"), but it's
heavier. Choose based on your needs:
- Use enums if you need runtime introspection or numeric enums.
- Use literal unions if you want compile-time-only types with no runtime overhead.
Complex literal unions
You can combine literal types with object types to create discriminated unions — we'll cover these in depth in Unions and Intersections, but here's a taste:
The literal "success" and "failure" act as discriminators — the
compiler uses them to narrow the type in each branch of the if.
Practice: a traffic light state machine
Let's solidify these concepts with a challenge. You'll model a traffic
light with three states ("red", "yellow", "green") and implement a
function that transitions between states.
Define a type TrafficLight = "red" | "yellow" | "green". Implement the function next(current: TrafficLight): TrafficLight that returns the next state in the cycle: red → green → yellow → red. The test checks that red → green, green → yellow, yellow → red.
Template literal types (a glimpse)
TypeScript 4.1+ supports template literal types, which let you construct new string literal types from existing ones:
This is an advanced feature (we'll revisit it when we cover advanced types), but it shows the power of literal types: you can compute new types from existing ones, all at compile time.
Check your understanding
What is the type of the variable x after this declaration: const x = "north";?
string (the general string type)
"north" (the literal type)
"north" | "south" | "east" | "west"
any
Summary
Type aliases let you name types for reuse and clarity. Literal types let
you express specific values in the type system — not just "a string,"
but this exact string. By combining literal types with unions, you
create lightweight enumerations that the compiler enforces at every
usage. The as const assertion turns object literals into deeply
readonly, literal-typed values, perfect for configuration and lookup
tables.
In the next chapter, Functions and Signatures, we'll see how to type function parameters, return values, and even the function itself as a first-class value.