Ch. 2 · TypeScript

TypeScript Template Literal Types

Compose string types with template literals, interpolate unions to generate combinations, and understand how they stay type-level only.

~2 min readintermediateupdated Oct 5, 2026

A template literal type builds a string type from other types, for example `user:${string}` or `${Size}-${Color}`. Interpolating a union distributes it into every combination, which makes these types a concise way to model event names, CSS-like tokens and prefixed identifiers.

Before you start

You should be comfortable with literal types, unions and generic constraints. This article builds on string literal types and shows how they combine.

Step-by-step walkthrough

Step 1: Build a string shape

type EventName = `on${Capitalize<'click' | 'focus'>}` produces 'onClick' | 'onFocus'. The template literal turns narrow literal types into a family of strings, and the checker matches them structurally, so a typo is rejected.

Step 2: Let unions distribute

When a union appears in a placeholder, the result is the union of all substitutions. `${'a' | 'b'}-${1 | 2}` yields 'a-1' | 'a-2' | 'b-1' | 'b-2'. This combinatorial expansion is powerful but grows quickly: keep the unions small or the type becomes unreadable and slows type-checking.

Step 3: Remember it is type-level only

Template literal types do not transform runtime strings. To turn 'user:1' into 1 at runtime you still parse the string; the type only describes what the string should look like. Pair them with runtime validation at boundaries, exactly as with any string type.

Worked scenario

The placeholder unions combine into every token name.

type Size = 'sm' | 'lg';
type Color = 'red' | 'blue';
type Token = `${Size}-${Color}`;
const a: Token = 'sm-red'; // ok
// const b: Token = 'md-red'; // error: 'md' is not a Size
TypeScript

Walk through the example

Token expands to 'sm-red' | 'sm-blue' | 'lg-red' | 'lg-blue'. Assigning 'sm-red' is allowed; 'md-red' fails because md is not one of the Size members. The compiler performs this expansion at compile time, so no runtime lookup table is needed to get the safety.

Common mistake

Overusing them for large unions such as `${A}-${B}-${C}` where each part has many members, which produces a huge union and slows the editor. Another mistake is expecting a template literal type to validate at runtime: it constrains what values are assignable, not what a parsed string contains.

Verify the behavior

Assign each valid combination and confirm it compiles, then assign an invalid token and read the error. Use satisfies with a template literal type to check a map of keys without widening. Finally, try a large union expansion and observe the hover size, which shows why keeping the sets small matters.

Interview exercise

You have routes like /users/42 and want their string shapes checked. Is a template literal type enough?

Answer and reasoning

It can constrain the shape, for example `/users/${number}`, so /user/42 is rejected. But it cannot verify that 42 is an existing user or that the number is an integer without decimals — /users/1.5 still matches `${number}`. Use the type to catch shape typos and a runtime parser or router to validate the actual identifier.

Continue learning

Combine this with key remapping in mapped types and literal inference. Read the TypeScript template literal types handbook and try the TypeScript interview questions.

More in TypeScript

read ✓TypeScript · hard

TypeScript Abstract Classes and Contracts

Share behavior with abstract classes, enforce required members, and decide when an interface or composition is the better contract.

~2 min readread →
read ✓TypeScript · mid

TypeScript Enums vs Union Types

Compare enums with unions of string literals: runtime cost, serialization, exhaustiveness and which one fits application code.

~3 min readread →
esc