Flash cards
Review the key moves
What is the main idea behind TypeScript Literal Types?
Lesson checks
Practice each idea before moving on
Short Mimo-style checks built from this lesson's code, terms, and sequence.
Which statement best captures the main point of this lesson?
Complete the missing token from the example code.
// A ___ with a string literal typePut the learning moves in the order that makes the concept easiest to apply.
Understanding Literal Types in TypeScript
Literal types in TypeScript allow you to specify exact values that variables can hold, providing more precision than broader types like string or number .
They are the building blocks for creating precise and type-safe applications.
Key Concepts
- String Literals : Exact string values like "success" | "error"
| - Numeric Literals : Specific numbers like 1 | 2 | 3 |
|---|
- Boolean Literals : Either true or false
- Template Literal Types : String literal types built using template string syntax
Common Use Cases
- Defining specific sets of allowed values
- Creating discriminated unions
- Type-safe event handling
- API response typing
- Configuration objects
String Literal Types
A string literal type represents a specific string value:
Example
// A variable with a string literal type
let direction: "north" | "south" | "east" | "west";
// Valid assignments direction = "north"; direction = "south"; // Invalid assignments would cause errors // direction = "northeast"; // Error: Type '"northeast"' is not assignable to type '"north" | "south" | "east" | "west"' // direction = "up"; // Error: Type '"up"' is not assignable to type '"north" | "south" | "east" | "west"' // Using string literal types in functions function move(direction: "north" | "south" | "east" | "west") { console.log(`Moving ${direction}`);
}
move("east"); // Valid
// move("up"); // Error: Argument of type '"up"' is not assignable to parameter of type...Numeric Literal Types
Similar to string literals, numeric literal types represent specific numeric values:
Example
// A variable with a numeric literal type
let diceRoll: 1 | 2 | 3 | 4 | 5 | 6;
// Valid assignments diceRoll = 1; diceRoll = 6; // Invalid assignments would cause errors // diceRoll = 0; // Error: Type '0' is not assignable to type '1 | 2 | 3 | 4 | 5 | 6' // diceRoll = 7; // Error: Type '7' is not assignable to type '1 | 2 | 3 | 4 | 5 | 6' // diceRoll = 2.5; // Error: Type '2.5' is not assignable to type '1 | 2 | 3 | 4 | 5 | 6' // Using numeric literal types in functions function rollDice(): 1 | 2 | 3 | 4 | 5 | 6 {
return Math.floor(Math.random() * 6) + 1 as 1 | 2 | 3 | 4 | 5 | 6;
}
const result = rollDice();
console.log(`You rolled a ${result}`);Boolean Literal Types
Boolean literal types are less commonly used since there are only two boolean values, but they can be useful in specific scenarios:
Example
// A type that can only be the literal value 'true' type YesOnly = true; // A function that must return true function alwaysSucceed(): true { // Always returns the literal value 'true'
return true;
}
// Boolean literal combined with other types type SuccessFlag = true | "success" | 1; type FailureFlag = false | "failure" | 0; function processResult(result: SuccessFlag | FailureFlag) {
if (result === true || result === "success" || result === 1) {
console.log("Operation succeeded");
} else {
console.log("Operation failed");
}
}
processResult(true); // "Operation succeeded"
processResult("success"); // "Operation succeeded"
processResult(1); // "Operation succeeded"
processResult(false); // "Operation failed"Literal Types with Objects
Literal types can be combined with object types to create very specific shapes:
Example
// Object with literal property values type HTTPSuccess = { status: 200 | 201 | 204; statusText: "OK" | "Created" | "No Content"; data: any;
};
type HTTPError = {
status: 400 | 401 | 403 | 404 | 500;
statusText: "Bad Request" | "Unauthorized" | "Forbidden" | "Not Found" | "Internal Server Error";
error: string;
};
type HTTPResponse = HTTPSuccess | HTTPError;
function handleResponse(response: HTTPResponse) {
if (response.status >= 200 && response.status < 300) {
console.log(`Success: ${response.statusText}`);
console.log(response.data);
} else {
console.log(`Error ${response.status}: ${response.statusText}`);
console.log(`Message: ${response.error}`);
}
}
// Example usage
const successResponse: HTTPSuccess = {
status: 200, statusText: "OK", data: { username: "john_doe", email: "john@example.com" }
};
const errorResponse: HTTPError = {
status: 404, statusText: "Not Found", error: "User not found in database"
};
handleResponse(successResponse);
handleResponse(errorResponse);Template Literal Types
TypeScript 4.1+ introduced template literal types, which allow you to create new string literal types by combining existing ones using template string syntax:
Example
// Basic template literals type Direction = "north" | "south" | "east" | "west"; type Distance = "1km" | "5km" | "10km"; // Using template literals to combine them type DirectionAndDistance = `${Direction}-${Distance}`; // "north-1km" | "north-5km" | "north-10km" | "south-1km" | ...
let route: DirectionAndDistance;
route = "north-5km"; // Valid
route = "west-10km"; // Valid
// route = "north-2km"; // Error // route = "5km-north"; // Error // Advanced string manipulation type EventType = "click" | "hover" | "scroll"; type EventTarget = "button" | "link" | "div"; type EventName = `on${Capitalize<EventType>}${Capitalize<EventTarget>}`; // "onClickButton" | "onClickLink" | "onClickDiv" | ... // Dynamic property access type User = { id: number; name: string; email: string; createdAt: Date;
};
type GetterName<T> = `get${Capitalize<string & keyof T>}`;
type UserGetters = {
[K in keyof User as GetterName<User>]: () => User[K];
};
// { getId: () => number; getName: () => string; ... } // String pattern matching type ExtractRouteParams<T extends string> = T extends `${string}:${infer Param}/${infer Rest}` ? Param | ExtractRouteParams<Rest> : T extends `${string}:${infer Param}` ? Param : never; type Params = ExtractRouteParams<"/users/:userId/posts/:postId">; // "userId" | "postId" // CSS units and values type CssUnit = 'px' | 'em' | 'rem' | '%' | 'vh' | 'vw'; type CssValue = `${number}${CssUnit}`;
let width: CssValue = '100px'; // Valid
let height: CssValue = '50%'; // Valid
// let margin: CssValue = '10'; // Error // let padding: CssValue = '2ex'; // Error // API versioning type ApiVersion = 'v1' | 'v2' | 'v3'; type Endpoint = 'users' | 'products' | 'orders'; type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE'; type ApiUrl = `https://api.example.com/${ApiVersion}/${Endpoint}`; // Complex example: Dynamic SQL query builder type Table = 'users' | 'products' | 'orders'; type Column<T extends Table> = T extends 'users' ? 'id' | 'name' | 'email' | 'created_at' : T extends 'products' ? 'id' | 'name' | 'price' | 'in_stock' : T extends 'orders' ? 'id' | 'user_id' | 'total' | 'status' : never; type WhereCondition<T extends Table> = { [K in Column<T>]?: { equals?: any; notEquals?: any; in?: any[];
};
};
function query<T extends Table>( table: T, where?: WhereCondition<T> ): `SELECT * FROM ${T}${string}` {
// Implementation would build the query
return `SELECT * FROM ${table}` as const;
}
// Usage
const userQuery = query('users', {
name: { equals: 'John' }, created_at: { in: ['2023-01-01', '2023-12-31'] }
});
// Type: "SELECT * FROM users WHERE ..."Do's and Don'ts
- Use literal types for fixed sets of values (enums, configuration options)
- Combine with union types for better type safety
- Use template literal types for string pattern matching
- Leverage type inference when possible
- Document the meaning of literal types
- Overuse literal types when a more general type would be better
- Create extremely large union types that hurt performance
- Use string literals when an enum would be more appropriate
Type Checking Performance
- Large union types can slow down type checking
- Complex template literal types can increase compilation time
- Consider using type aliases for complex literal types
- Be mindful of TypeScript's recursion depth limits