Dataslope logoDataslope

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:

Code Block
TypeScript 5.7

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:

Code Block
TypeScript 5.7

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:

  1. Syntax for objects: interface uses a declaration block; type uses an assignment:
    interface Point { x: number; y: number }
    type Point = { x: number; y: number };
  2. Unions and intersections: type can express unions (A | B) and intersections (A & B). interface cannot.
  3. Declaration merging: interface supports it; type does not.
  4. Extension syntax: interface uses extends; type uses &.

Here's a comparison:

Code Block
TypeScript 5.7

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":

Code Block
TypeScript 5.7

By itself, a single literal type is not very useful. But when you combine several with a union, you get a lightweight enumeration:

Code Block
TypeScript 5.7

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.

Code Block
TypeScript 5.7

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:

Code Block
TypeScript 5.7

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:

Code Block
TypeScript 5.7

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:

Code Block
TypeScript 5.7

The as const assertion does two things:

  1. It narrows each property to its literal type ("production" instead of string, 3000 instead of number).
  2. 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:

Code Block
TypeScript 5.7

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:

Code Block
TypeScript 5.7

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:

Code Block
TypeScript 5.7

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.

Challenge
TypeScript 5.7
Traffic Light Transition

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:

Code Block
TypeScript 5.7

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

QuestionSelect one

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.

On this page