Skip to main content

TypeScript Literal Types

A literal type is a type that allows exactly one value — for example the string "north" or the number 1 — instead of any string or any number.


Understanding Literal Types in TypeScript

Literal types let you say exactly which values a variable may hold. They are more precise than the broad types string, number or boolean.

They are the building blocks for precise, type-safe code.

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 with template string syntax

Common use cases:

  • Defining a fixed set of allowed values
  • Creating discriminated unions
  • Type-safe event handling
  • Typing API responses
  • Configuration objects

String Literal Types

A string literal type stands for one specific string value. Several of them are usually combined with | into a union of allowed values.

Example

direction may only hold one of four strings. Any other string, such as "northeast", is a compile error, both when assigning to the variable and when calling move.

Prompt
Show a string literal union type used for a variable and a function parameter. Declare direction with the type "north" | "south" | "east" | "west", assign "north" and then "south", and leave commented-out assignments of "northeast" and "up" with the errors they would cause. Write a move function taking the same literal union that logs Moving <direction>, call it with "east", and leave move("up") commented out as an invalid call.
Output: TypeScript file and captured output

Numeric Literal Types

Just like string literals, numeric literal types stand for specific number values.

Example

diceRoll accepts only the numbers 1 to 6. Because Math.random() returns a plain number, rollDice needs a type assertion to promise it returns one of the six literals. The printed roll changes on every run.

Prompt
Show a numeric literal union type for a dice value. Declare diceRoll with the type 1 | 2 | 3 | 4 | 5 | 6, assign 1 and then 6, and leave commented-out assignments of 0, 7 and 2.5 with their errors. Write rollDice() whose return type is the same union and which returns Math.floor(Math.random() * 6) + 1 cast to that union, then log You rolled a <result> (the number is random on each run).
Output: TypeScript file and captured output

Boolean Literal Types

Boolean literal types are used less often, since there are only two boolean values, but they are useful in some cases — for example to say a function always returns true.

Example

alwaysSucceed must return the literal true. SuccessFlag and FailureFlag mix boolean, string and numeric literals, and processResult tells them apart with simple equality checks.

Prompt
Show boolean literal types on their own and combined with other literals. Declare type YesOnly = true and an alwaysSucceed() function whose return type is the literal true. Then declare SuccessFlag = true | "success" | 1 and FailureFlag = false | "failure" | 0, and a processResult function taking either that logs Operation succeeded when the value is true, "success" or 1, and Operation failed otherwise. Call it with true, "success", 1 and false.
Output: TypeScript file and captured output

Literal Types with Objects

Literal types can be used as property types inside object types. This gives you very specific object shapes.

Example

HTTPSuccess and HTTPError restrict status and statusText to a few literal values each. A plain range check on status does not narrow the union, so the example uses a small isSuccess type guard; inside each branch data or error can then be used safely.

Prompt
Show literal types used as property types inside object types to model HTTP responses. Declare HTTPSuccess (status: 200 | 201 | 204, statusText: "OK" | "Created" | "No Content", data: any) and HTTPError (status: 400 | 401 | 403 | 404 | 500, the matching statusText literals, error: string), plus HTTPResponse as their union. Write an isSuccess type guard (response is HTTPSuccess) that checks the status is in the 200 range, and a handleResponse function that uses it to log Success: <statusText> and the data for a success, and otherwise logs Error <status>: <statusText> and Message: <error>. Call it with a 200 "OK" response carrying { username: "john_doe", email: "[email protected]" } and a 404 "Not Found" response with the error User not found in database.
Output: TypeScript file and captured output

Template Literal Types

Since TypeScript 4.1 you can build new string literal types by combining existing ones with template string syntax, using backticks and ${...} placeholders. Every combination of the placeholder unions becomes a member of the new union.

Example

The example goes from a simple DirectionAndDistance union to more advanced uses: Capitalize to build event names, a mapped type that creates getter names, infer to pull parameter names out of a route string, a CssValue type of number plus unit, and a typed SQL query builder. The file ends with export {} so its local EventTarget type does not clash with the built-in DOM type.

Prompt
Show template literal types built from string literal unions, from basic to advanced. Combine Direction and Distance unions into DirectionAndDistance (`${Direction}-${Distance}`) and assign valid routes such as "north-5km" to a route variable. Then build EventName with Capitalize, a GetterName<T>/UserGetters mapped type over a User type, a recursive ExtractRouteParams<T> that pulls :param names out of a path with infer, a CssValue type of `${number}${CssUnit}`, an ApiUrl type, and a query<T extends Table> function whose return type is `SELECT * FROM ${T}${string}` called for the users table with a WhereCondition. Leave invalid assignments commented out. Nothing is printed. Add export {}; at the end so the local EventTarget type does not clash with the built-in DOM global.
Output: TypeScript file and captured output

Best Practices

Do:

  • Use literal types for fixed sets of values (enum-like options, configuration values)
  • Combine them with union types for better type safety
  • Use template literal types for string pattern matching
  • Let type inference work for you where possible
  • Document what each literal type means

Don't:

  • Overuse literal types when a more general type would be better
  • Create extremely large union types that slow down type checking
  • Use string literals when an enum would be a better fit

Performance Considerations

  • Large union types can slow down type checking
  • Complex template literal types can increase compile time
  • Use type aliases to name complex literal types instead of repeating them
  • Keep TypeScript's recursion depth limits in mind for recursive template literal types