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 SizeWalk 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.