Literal Types
Not just any string — THIS string.
TypeScript doesn't just let you type broad categories like string or number — you can type the exact, specific value. Imagine saying "not just any string, but EXACTLY the word 'biryani'." These are literal types, and they're incredibly powerful for creating precise type constraints.
Think of ordering at a Hyderabadi wedding feast. You don't just say "give me food" — that's like using string, the broadest category possible. You don't even say "give me biryani" — that's a broader category (like a union of biryani types). You say "give me EXACTLY the Hyderabadi Dum Biryani from the wedding menu — not Lucknowi, not Kolkata, not Ambur — EXACTLY Hyderabadi Dum." The chef will not accept any other dish name. That's a literal type — it's not a category, it's a specific exact value.
type Biryani = "hyderabadi-dum"
// Only "hyderabadi-dum" is accepted
// "lucknowi"? REJECTED.
// "kolkata"? REJECTED.
// Nothing else will work!
With type Biryani = "hyderabadi-dum", the only value this type accepts is the exact string "hyderabadi-dum". Nothing else passes. You can also combine literal types with unions to create a set of allowed exact values:
type YesNo = "yes" | "no"
// Only two specific strings allowed
// "maybe" is NOT allowed
Seedha samjho: literal types = exact values as types, not categories. They're the most precise typing you can get — narrowing down from string (any string at all) to "hyderabadi-dum" (one specific string only). This precision is what makes TypeScript so powerful for catching bugs at compile time rather than at runtime, where it's already too late.
You can use specific strings as types — not just the broad string type, but exact string values. The syntax is straightforward: you write the string value itself as the type, and combine multiple options with the union operator |.
type Direction =
| "north"
| "south"
| "east"
| "west"
Now Direction only accepts those four exact strings. Try anything else and TypeScript slams the door shut:
let move: Direction = "north" // ✓
let move: Direction = "up" // ✗ ERROR!
The error message will clearly tell you that "up" is not assignable to type Direction. This is incredibly useful in real-world code. Here are practical examples:
- Function parameters with specific options:
function setMode(
mode: "light" | "dark" | "system"
) {
// mode can only be one of three
}
- API response status types:
type Status =
| "loading"
| "success"
| "error"
- CSS property values:
type Overflow =
| "visible"
| "hidden"
| "scroll"
| "auto"
String literals combine with unions to create constrained choices — like an enum but without the enum overhead. No extra JavaScript code is generated; it's purely a compile-time check.
Here's a crucial detail: TypeScript's type inference for const declarations creates literal types automatically! When you write const x = "hello", x's type is the literal "hello", not string. But when you write let x = "hello", x's type is the broad string. This is because const variables can never be reassigned, so TypeScript knows the value will always be exactly that literal. With let, TypeScript assumes you might reassign it later, so it widens the type. This is why const gives you more precise types — use it whenever possible!
The same literal type concept works for numbers and booleans, not just strings. Any primitive value can be used as a type itself.
Number literal types let you restrict a type to specific numeric values:
type DieRoll = 1 | 2 | 3 | 4 | 5 | 6
type HttpStatusCode =
| 200 | 201
| 400 | 401 | 403
| 404 | 500
With DieRoll, only the numbers 1 through 6 are accepted. A value of 7 would be a type error. With HttpStatusCode, you can precisely model the HTTP codes your application actually handles.
Boolean literal types are even more specific. The type true can ONLY ever be true:
type Truth = true
// Only "true" is accepted, not false!
Practical examples of number and boolean literals in action:
function setViewport(
scale: 0.5 | 1 | 1.5 | 2
) {
// only these exact scale values
}
type Falsy =
| false
| 0
| ""
| null
| undefined
Notice that true as a type is different from boolean — true only accepts true, while boolean accepts true or false. The same narrowing principle applies: literal types are subsets of their broader counterparts.
You can also combine literal types with broader types in a single union:
type ID =
| string
| number
| null
| undefined
Numeric literal types are rare compared to string literals, but they're invaluable for specific protocols, API status codes, game states, fixed-precision scales, and configuration constants. They turn magic numbers into self-documenting type constraints.
TypeScript 4.0+ also supports bigint literals:
type BigNum = 100n | 200n
This gives you the same exact-value constraint for BigInt values, which is useful when working with large numeric identifiers or high-precision calculations.
Literal types come with some tricky traps that catch beginners off guard. Let's walk through the most common ones so you don't get stuck.
Trap 1: const vs let inference. This is the #1 gotcha with literal types. When you declare a variable with const, TypeScript infers the literal type. But with let, it widens to the broader type:
const x = "hello"
// Type: "hello" (literal) ✓
let y = "hello"
// Type: string (widened!) ✗
When you pass a let variable to a function expecting a literal type, it fails. The fix? Use as const to force the literal type:
let dish = "hyderabadi" as const
// Type: "hyderabadi" (forced) ✓
function serve(
b: "hyderabadi" | "lucknowi"
) {}
serve(dish) // ✓ Works now!
Trap 2: Object property inference. Even when you declare an object with const, the properties inside are NOT inferred as literal types:
const obj = { status: "loading" }
// obj.status is string, NOT "loading"!
function handle(
s: "loading" | "done"
) {}
handle(obj.status) // ✗ ERROR!
The fix is as const on the whole object:
const obj = { status: "loading" } as const
// obj.status is "loading" ✓
handle(obj.status) // ✓ Works!
Trap 3: Literal widening in arrays. When you create an array literal, TypeScript widens the element types:
const arr = ["hello", "world"]
// Type: string[] — widened!
const arr2 = ["hello","world"] as const
// Type: readonly ["hello","world"] ✓
With as const, the array becomes a readonly tuple with literal element types. Without it, every element is just string.
Trap 4: Exhaustiveness checking. If you add a new literal to a union but forget to handle it in your switch/if, TypeScript won't always warn you unless you use the never check (covered in detail in Chapter 3.5). For now, just know this is a potential gap — always verify you're handling every case in your union.
Here's your complete cheatsheet for literal types. Bookmark this, screenshot it, tattoo it on your arm — whatever works!
// String literal type
type S = "hello" | "world"
// Number literal type
type N = 1 | 2 | 3
// Boolean literal type
type B = true
// Combined literal types
type Mix =
| "yes" | "no"
| 1 | 0
| true
Inference rules:
// Const → infers literal type
const x = "hi" // type: "hi"
// Let → infers broad type
let y = "hi" // type: string
// Force literal with as const
let z = "hi" as const // type: "hi"
Object and array literals:
// Object as const
const obj = { s: "ok" } as const
// obj.s type: "ok"
// Array as const
const arr = ["a","b"] as const
// type: readonly ["a","b"]
Five key rules to remember:
- Rule 1: Literal types accept ONLY that exact value — no "almost" or "close enough."
- Rule 2:
constinfers literal types;letinfers broad types (widening). - Rule 3: Use
as constto force literal inference on any value. - Rule 4: Combine literal types with unions for constrained choices.
- Rule 5: Literal widening is automatic for
let— always be aware of it.
The golden rule: "Literal types are the strict bhai at the gate — only the exact name on the list gets in, no 'almost' or 'close enough'!" They give you the tightest possible type safety, catching typos and invalid values before your code ever runs. Use them generously for configuration, status fields, and any value that should only be one of a fixed set of options.
Key Takeaways
- Literal types represent exact values — "hello", 42, true — not categories
- const infers literal types; let infers broad types (widening)
- Use as const to force literal type inference on any value
- Combine literal types with unions for constrained choices
- Object properties and array elements widen by default — use as const
Want to track your progress?
Log in to save your place and pick up where you left off.
Progress track karna chahte ho?
Login karo apni progress save karne ke liye aur jahan chhoda tha wahan se shuru karo.
Login