Template literal types
String types you can compute.
Template literal types apply backtick interpolation at the type level β ` on${Capitalize<E>} ` β letting the compiler build, constrain, and even parse string types from other types instead of treating every string the same.
Strings as structure, not soup
A plain string type says nothing about a value's shape. A template literal type uses the same backtick-and-${} syntax you know from runtime strings, but the placeholders hold types, not values. type Greeting = `Hello, ${string}!` matches "Hello, Ada!" and "Hello, world!" but rejects "Goodbye" β the literal parts must line up exactly. Swap string for a union or a generic parameter and the compiler enumerates or constrains the result for you.
The four intrinsic string types
TypeScript ships four built-in helpers that only make sense inside template literals: Uppercase<S>, Lowercase<S>, Capitalize<S>, and Uncapitalize<S>. They transform the literal string type character-wise at compile time. Pair them with interpolation to derive things like event-handler names β `on${Capitalize<E>}` turns "click" into "onClick" β without writing a single runtime line.
Distribution turns one pattern into many
When a placeholder is a union, the template distributes over every member and produces the cross-product. `${'sm' | 'lg'}-${'red' | 'blue'}` expands to "sm-red" | "sm-blue" | "lg-red" | "lg-blue". This is how a handful of literal unions generate a complete, exhaustive set of valid class names, CSS units, or route keys β the type system does the combinatorics so a typo can't slip through.
Parsing strings back apart with infer
Template literals also work in reverse. Inside a conditional type, a placeholder can be infer X, capturing the matching slice as a new type. T extends `/${infer Param}` ? Param : never pulls the segment after the slash out of a route string. Recurse on the tail and you can extract every :param from "/users/:id/posts/:postId" into a typed object β a tiny parser that runs entirely in the checker.
Try it 5 examples
A greeting type with a literal placeholder
TSintrogreet('Ada') returns "Hello, Ada!" and greet('world') returns "Hello, world!" β the same literal shape the Greeting<T> type encodes, so the runtime builder's return type is the template literal type itself, not a loose string.