string represents an infinite set of text values. A Literal Type represents an exact, singular unit value (such as "admin" or 404). Combining literal types into unions provides type safety with zero runtime JavaScript overhead.1. What are Literal Types?#
In set theory, a type containing exactly one value is called a Unit Type (or Singleton Type). In TypeScript, these are called Literal Types.
// 'mode' cannot hold any string—it can ONLY hold the exact string "dark"
let mode: "dark" = "dark";
// ❌ Compiler Error: Type '"light"' is not assignable to type '"dark"'.
// mode = "light";
TypeScript supports four forms of literal types:
- String Literals:
"GET","POST","admin" - Numeric Literals:
200,404,500,3.14 - Boolean Literals:
true,false(Notice thatbooleanis just a built-in union alias fortrue | false!) - BigInt Literals:
100n,0n
2. Literal Unions (Zero-Overhead Enum Replacement)#
A single literal type is rarely useful on its own. However, when combined into a Union of Literals, they form one of TypeScript’s most powerful features:
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
function makeRequest(url: string, method: HttpMethod) {
console.log(`Sending ${method} request to ${url}`);
}
makeRequest("https://api.acme.com/users", "GET"); // 🟢 Valid!
// ❌ Compiler Error: Argument of type '"CONNECT"' is not assignable to parameter of type 'HttpMethod'.
// makeRequest("https://api.acme.com/users", "CONNECT");
Why Literal Unions beat Enums in TypeScript#
| Feature | String Literal Unions ("a" | "b") | TypeScript Enums (enum Role) |
|---|---|---|
| Runtime JS Code | Zero bytes (Completely erased) | Emits boilerplate JS IIFE objects |
| JSON Serialization | Direct string values ("admin") | Requires mapping enum object keys |
| Structural Compatibility | Pass string literal "admin" directly | Requires Role.Admin reference |
| Bundle Impact | Tree-shakes perfectly | Leaves dead enum objects in bundle |
3. Numeric Literal Unions for Domain Rules#
You can use numeric literal unions to enforce strict domain boundaries (such as HTTP status codes, dice rolls, or specific port numbers):
type HttpStatus = 200 | 201 | 400 | 401 | 403 | 404 | 500;
interface ApiResponse {
status: HttpStatus;
data: unknown;
}
const res: ApiResponse = {
status: 200, // Valid
data: { user: "Alice" },
};
// ❌ Error: Type '202' is not assignable to type 'HttpStatus'.
// res.status = 202;
4. The Object Property Widening Trap#
A very common bug occurs when passing an object property containing a string literal to a function expecting a Literal Union.
Consider this scenario:
type HttpMethod = "GET" | "POST";
function fetchUrl(url: string, method: HttpMethod) {
// ...
}
// Declare configuration object with 'const'
const reqConfig = {
url: "https://api.example.com",
method: "GET",
};
// ❌ Compiler Error: Argument of type 'string' is not assignable to parameter of type 'HttpMethod'.
// fetchUrl(reqConfig.url, reqConfig.method);
Why did this happen?#
Even though reqConfig was declared using const, JavaScript objects are mutable by default. You can change reqConfig.method = "PUT" on the very next line without reassigning reqConfig.
Therefore, TypeScript infers reqConfig.method as broad string rather than literal "GET".
3 Solutions to the Widening Trap#
Solution 1: Inline Type Annotation on Object#
Explicitly annotate the object property with the literal union:
const reqConfig: { url: string; method: HttpMethod } = {
url: "https://api.example.com",
method: "GET",
};
fetchUrl(reqConfig.url, reqConfig.method); // 🟢 WORKED!
Solution 2: Type Assertion (as const)#
Assert the literal type on the property value or the entire object:
const reqConfig = {
url: "https://api.example.com",
method: "GET" as const, // Locks down method to literal "GET"
};
fetchUrl(reqConfig.url, reqConfig.method); // 🟢 WORKED!
Solution 3: Full Object Immutability (as const)#
Lock down all properties of the object to literal types and readonly:
const reqConfig = {
url: "https://api.example.com",
method: "GET",
} as const; // Inferred type: { readonly url: "https://api.example.com"; readonly method: "GET" }
fetchUrl(reqConfig.url, reqConfig.method); // 🟢 WORKED!
Summary & Next Steps#
In this episode:
- We defined unit types (Singletons) like
"GET",404,true. - We proved why String Literal Unions are preferred over Enums due to zero runtime footprint and clean JSON serialization.
- We investigated Object Property Literal Widening and solved it using
as const.
In Episode 9: Type Narrowing with typeof and instanceof, we will learn how TypeScript’s control flow analysis narrows broad types down to specific concrete types!

