Leverage template literal types to generate unions through Cartesian products and parse string literals with infer.
Open this lesson in KodokonTemplate literal types bring template literal syntax into the world of types. Interpolate a union and the compiler computes the Cartesian product: every possible combination becomes a member of the result. This is the tool that lets you type event keys, routes, or design-system tokens down to the exact character.
type Lang = "fr" | "en";
type Theme = "light" | "dark";
type ThemeKey = `${Lang}-${Theme}`;
// "fr-light" | "fr-dark" | "en-light" | "en-dark"The real power shows up when combined with infer: a template literal type becomes a matching pattern over strings. The compiler slices the input literal to match each segment; when several splits are possible, the first occurrence of the separator wins. Add recursion and you get genuine parsers evaluated at compile time.
type Split<S extends string, Sep extends string> =
S extends `${infer Head}${Sep}${infer Rest}`
? [Head, ...Split<Rest, Sep>]
: [S];
type Parts = Split<"a.b.c", ".">;
// ["a", "b", "c"]This pattern shines when extracting the parameters of an Express-style route. Each recursive call consumes one :param segment, and the final union lists every parameter name, which you can then feed into a Record to type the handler's signature.
type Params<Path extends string> =
Path extends `${string}:${infer P}/${infer Rest}`
? P | Params<Rest>
: Path extends `${string}:${infer P}`
? P
: never;
type RouteParams = Params<"/users/:id/posts/:postId">;
// "id" | "postId"TypeScript ships four intrinsic utilities - Uppercase, Lowercase, Capitalize, Uncapitalize - implemented directly inside the compiler (the intrinsic keyword), so you cannot rewrite them yourself. Another production pattern: string & {}. In a union, string would absorb the literals and the editor would lose autocompletion; intersecting with {} creates an equivalent but distinct type that the compiler does not reduce.
type Method = "get" | "post";
type Handler = `on${Capitalize<Method>}`;
// "onGet" | "onPost"
type KnownColor = "red" | "green" | "blue";
type Color = KnownColor | (string & {});
declare function paint(color: Color): void;
paint("red"); // suggested by autocompletion
paint("#ff8800"); // also acceptedA has 3 members and B has 4, how many members does ${A}-${B} contain?Capitalize produce when applied to the literal "hello world"?KnownColor | (string & {}) rather than KnownColor | string?