10 questions found
What is a template literal type, and how does its syntax resemble a JavaScript template literal?
Beginner
A template literal type uses the same backtick syntax as a runtime template literal, but with TYPES embedded inside `${...}` instead of values — it produces a new string literal type (or union of them) built from combining literal text with the possible values of the embedded type(s).
type Greeting = `Hello, ${string}!`;
const a: Greeting = 'Hello, Sam!'; // OK
// const b: Greeting = 'Hi, Sam!'; // Error: doesn't match the pattern
Real-world example
Constraining a string parameter to follow a specific required format, like a greeting or a CSS custom property name.
Common follow-ups: What happens if you embed a UNION type (not just 'string') inside a template literal type?
Types & Interfaces
How does embedding a union of literal types inside a template literal type produce a new union?
Beginner
When you embed a union type inside `${...}`, TypeScript computes the CROSS PRODUCT of every combination, producing a new union of every possible resulting string literal — this happens automatically, without any extra syntax needed.
type Size = 'small' | 'medium' | 'large';
type ClassName = `btn-${Size}`;
// 'btn-small' | 'btn-medium' | 'btn-large'
Real-world example
Generating a precise union of valid CSS class names or Tailwind utility classes from a smaller set of literal building blocks.
Common follow-ups: What happens if you combine TWO different union types inside the same template literal type?
Union & Intersection Types
How do the built-in intrinsic string manipulation types (Uppercase, Lowercase, Capitalize, Uncapitalize) work with template literal types?
Intermediate
These four built-in generic utility types transform a string literal type's CASING at the type level (mirroring their runtime string method equivalents), and are most commonly combined with template literal types to systematically derive new property/method names with consistent casing conventions.
type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickEvent = EventName<'click'>; // 'onClick'
type HoverEvent = EventName<'hover'>; // 'onHover'
Real-world example
Deriving consistent event-handler prop names (onClick, onHover) automatically from a base set of event name literals.
Common follow-ups: Do these intrinsic types have any runtime equivalent, or do they exist purely at the type level?
Mapped Types
How would you use a template literal type to validate that a string matches a specific structured pattern, like a hex color code?
Intermediate
Build a template literal type describing the exact expected character pattern using nested unions for each character position — while genuinely complex patterns (like arbitrary-length hex codes) hit practical limits, simpler fixed-length patterns work well and give real compile-time format validation.
type HexDigit = '0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'|'a'|'b'|'c'|'d'|'e'|'f';
type HexColor = `#${HexDigit}${HexDigit}${HexDigit}${HexDigit}${HexDigit}${HexDigit}`;
const valid: HexColor = '#ff5733'; // OK
// const invalid: HexColor = '#zzz'; // Error: doesn't match pattern
Real-world example
Enforcing that a configuration value or design-token color string is a syntactically valid 6-digit hex code at compile time.
Common follow-ups: Why does this specific approach become impractical for validating longer or more complex patterns, like full email address validation?
Union & Intersection Types
How do template literal types combine with mapped type key remapping (the 'as' clause) to auto-generate getter/setter method names?
Intermediate
Within a mapped type's key-remapping 'as' clause, you can construct a NEW property name using a template literal type that references the original key (often combined with Capitalize<>), letting you systematically derive a full set of consistently-named methods or properties from an existing type's keys.
type Setters<T> = {
[K in keyof T as `set${Capitalize<string & K>}`]: (value: T[K]) => void
};
interface Person { name: string; age: number; }
type PersonSetters = Setters<Person>;
// { setName: (value: string) => void; setAge: (value: number) => void }
Real-world example
Auto-generating a complete, consistently-named setter interface from a plain data model interface without manual duplication.
Common follow-ups: Why does the key need to be written as `string & K` rather than just `K` inside the template literal here?
Mapped Types
How would you use 'infer' inside a template literal type's conditional check to parse and extract a portion of a string literal type?
Advanced
Combine a conditional type with a template literal PATTERN on the 'extends' side, using 'infer' at the specific position you want to capture — TypeScript pattern-matches the string literal against the template shape and extracts the inferred substring as its own type.
type ExtractRouteParam<T extends string> =
T extends `${string}:${infer Param}` ? Param : never;
type Param1 = ExtractRouteParam<'/users/:id'>; // 'id'
type Param2 = ExtractRouteParam<'/posts/:postId'>; // 'postId'
Real-world example
Building a fully type-safe router library that extracts route parameter names directly from a route path's literal string type.
Common follow-ups: How would you extend this to extract MULTIPLE route parameters from a path with several ':param' segments?
Conditional Types
How would you build a fully type-safe route-parameter object type (like Express or React Router param typing) using recursive template literal parsing?
Advanced
Recursively walk the route string literal type using conditional types with 'infer', peeling off one `:param` segment at a time, and use a mapped type (via Record) to assemble the final object type mapping every discovered parameter name to 'string' — this is exactly the technique modern type-safe routing libraries use internally.
type ParseParams<T extends string> =
T extends `${string}:${infer Param}/${infer Rest}`
? { [K in Param | keyof ParseParams<Rest>]: string }
: T extends `${string}:${infer Param}`
? { [K in Param]: string }
: {};
type Params = ParseParams<'/users/:userId/posts/:postId'>;
// { userId: string; postId: string }
Real-world example
Building a fully type-safe routing library where navigating to a route automatically requires and types its exact set of URL parameters.
Common follow-ups: What real-world libraries (like React Router's typed routes, or tRPC) rely on exactly this kind of type-level string parsing technique?
Conditional Types
How do template literal types interact with number and bigint literal types embedded inside them?
Advanced
Embedding a `number` or `bigint` type inside a template literal type converts it to its STRING representation in the resulting type (matching how JavaScript's actual template literals coerce numbers to strings at runtime) — embedding a literal number type produces that number's specific string form, while embedding the general 'number' type produces a much broader (though still constrained) string pattern.
type Pixels<N extends number> = `${N}px`;
type Width = Pixels<100>; // '100px' -- a specific string literal
type AnyPixelValue = `${number}px`; // matches '0px', '42px', '3.5px', etc. -- any numeric string followed by 'px'
Real-world example
Typing CSS-in-JS style dimension values that must be a valid number immediately followed by a unit suffix like 'px' or '%'.
Common follow-ups: Does embedding a 'boolean' type inside a template literal type behave similarly, producing 'true' | 'false' as string literals?
Primitive Types
Arrays & Tuples
How would you use template literal types to enforce a semantic-versioning-style string format at compile time?
Advanced
Build a template literal type combining number-embedding for each version segment with literal '.' separators, giving reasonable structural validation of the major.minor.patch shape — though note this can't validate that each segment stays within a specific numeric RANGE, since 'number' embedded in a template literal accepts any numeric string.
type SemVer = `${number}.${number}.${number}`;
const valid: SemVer = '1.4.2'; // OK
// const invalid: SemVer = '1.4'; // Error: missing the patch segment
Real-world example
Enforcing that a package version string field follows the expected major.minor.patch structural shape before it's used elsewhere.
Common follow-ups: Why can't this technique alone prevent an invalid value like '99999.0.0' if there's a business rule against versions that high?
Primitive Types
Arrays & Tuples
How would you implement a type-safe 'path getter' utility (like lodash's get()) using recursive template literal types to validate dot-separated property paths?
Advanced
Recursively split the dot-separated path string literal type using 'infer' to peel off one segment at a time, checking at each step that the segment is actually a valid key of the CURRENT nested object type, and recursing into that property's type for the next segment — producing full compile-time validation of arbitrarily deep property paths.
type PathValue<T, Path extends string> =
Path extends `${infer Key}.${infer Rest}`
? Key extends keyof T
? PathValue<T[Key], Rest>
: never
: Path extends keyof T
? T[Path]
: never;
interface State { user: { profile: { name: string } } }
type NameType = PathValue<State, 'user.profile.name'>; // string
type BadType = PathValue<State, 'user.profile.age'>; // never -- 'age' doesn't exist
Real-world example
Building a fully type-safe deep-property-access utility that catches invalid dot-path typos at compile time, unlike lodash's untyped runtime-only get().
Common follow-ups: Why does this kind of deeply recursive type-level parsing sometimes hit the TypeScript compiler's recursion depth limits on very large object types?
Conditional Types