Annotated solution
Published September 6, 2026The solution
type SnakeCase<S extends string> = S extends `${infer H}${infer R}` ? H extends Uppercase<H> ? H extends Lowercase<H> ? `${H}${SnakeCase<R>}` : `_${Lowercase<H>}${SnakeCase<R>}` : `${H}${SnakeCase<R>}` : S
The common wrong answer
type SnakeCase<S extends string> = S extends `${infer H}${infer R}` ? H extends Uppercase<H> ? `_${Lowercase<H>}${SnakeCase<R>}` : `${H}${SnakeCase<R>}` : S
"1" extends Uppercase<"1"> is true, because Uppercase leaves anything that is not a lowercase letter exactly as it found it. So every digit, space and symbol is treated as a capital and gets its own underscore: "a1B" comes out as "a_1_b". Case-ness is not one test but two — equal to your uppercase form, *and* different from your lowercase one.
Line by line
`${infer H}${infer R}`Two placeholders with nothing between them split off exactly one character. This is the type-level equivalent of walking a string one index at a time.
H extends Lowercase<H> ? ... : ...The second test is what excludes non-letters. A digit is equal to both its uppercase and its lowercase form, so it lands in the branch that passes it through unchanged.
: SThe empty string matches no one-character pattern, so it falls to the base case and returns itself — which is also what stops the recursion.
Takeaway
Uppercase and Lowercase are total functions: they never fail, they just do nothing to characters that have no case. Any test built on one of them alone will quietly misclassify every digit and symbol you meet.

