Literal Types & const Assertions
A literal type restricts a value to one exact constant — like the type "active" or 42 — and combined into unions they give you a precise, enum-like set of allowed values.
Learn Literal Types & const Assertions in our free TypeScript course — an interactive lesson with runnable examples, a practice exercise and a quick reference.
Part of the free TypeScript course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
A plain string type is like a blank text box — you can type anything . A literal union is like a dropdown menu : the field still holds a string, but only the specific options on the list are valid. "S" | "M" | "L" is the dropdown; "XXL" simply isn't on it, so the compiler rejects it before your program ever runs.
1. Literal Types: One Exact Value
A literal type is a type made of a single constant value. "active" as a type accepts only the string "active" ; 6 as a type accepts only the number 6 . By themselves they're rarely useful — their power shows when you join several with | to form a fixed set, like "north" | "south" | "east" | "west" .
2. Widening vs Narrowing
When you initialise a variable, TypeScript decides between the broad type and the exact literal. A let can be reassigned, so TypeScript widens let theme = "dark" to string . A const never changes, so it narrows const fixed = "dark" to the literal type "dark" . Understanding this explains a lot of "why is my type just string ?" surprises.
3. as const Assertions
An as const assertion tells TypeScript: "infer the narrowest possible type and make it readonly ." Object fields keep their literal types instead of widening, arrays become readonly tuples of literals, and you can derive a union directly from data: turns ["sm","md","lg"] as const into "sm" | "md" | "lg" — one source of truth for both the values and the type.
🎯 Your Turn
Model a traffic light whose colour is one of three literals. Fill in the two blanks marked ___ so the cycle goes red → green → amber → red, then run it.
No blanks this time — just a brief and a starting outline. Build the literal-typed state machine yourself, run it, and check your output against the example in the comments.
Practice quiz
What is a literal type?
- A type that accepts any string
- A runtime literal value
- A type whose only value is one exact constant
- A union of all numbers
Answer: A type whose only value is one exact constant. A literal type like "active" or 42 accepts only that one exact value.
What does a union of literals like "sm" | "md" | "lg" give you?
- A precise, enum-like set of allowed values
- A random value
- Any string
- A tuple
Answer: A precise, enum-like set of allowed values. Joining literals with | creates a fixed set of allowed options, like a dropdown menu.
What type does let theme = "dark" infer?
- "dark"
- any
- literal
- string
Answer: string. A let can be reassigned, so TypeScript widens it to string.
What type does const fixed = "dark" infer?
- string
- "dark"
- any
- char
Answer: "dark". A const never changes, so it narrows to the literal type "dark".
What does as const do to a value?
- Freezes it into its narrowest readonly literal form
- Makes it mutable
- Converts it to a string
- Removes its type
Answer: Freezes it into its narrowest readonly literal form. as const infers the narrowest possible type and makes the value readonly.
For const SIZES = ["sm", "md", "lg"] as const, what is typeof SIZES[number]?
typeof ARR[number] on an as const array yields the union of its element literals.
Why might passing a let string to a function expecting "dark" | "light" fail?
- The value is wrong
- The let was widened to string, which is not assignable to the literal union
- Functions cannot take strings
- let is not allowed in TypeScript
Answer: The let was widened to string, which is not assignable to the literal union. Even with a correct value, a widened string is not assignable to the narrower literal union.
Which is a valid numeric literal union type?
- type Dice = number(6)
- type Dice = int<6>
Numeric literals combine with | just like strings: 1 | 2 | 3 | 4 | 5 | 6.
Compared with an enum, a literal union of strings is generally...
- Heavier and needs runtime code
- Lighter, needs no runtime object, and is type-safe
- Unsafe
- Only for numbers
Answer: Lighter, needs no runtime object, and is type-safe. A literal union is lighter and needs no runtime object; most teams prefer it for string sets.
Why declare options as const OPTS = [...] as const and derive the type with typeof OPTS[number]?
- It runs faster
- It disables type checking
- It keeps a single source of truth so the array and type stay in sync
- It is required syntax
Answer: It keeps a single source of truth so the array and type stay in sync. Deriving the type from the array means changing the array updates the type automatically.