CamelCase
CamelCase
Challenge
Implement CamelCase<T> which converts a snake_case string into camelCase.
type camelCase1 = CamelCase<'hello_world_with_types'>
// expected: 'helloWorldWithTypes'
type camelCase2 = CamelCase<'HELLO_WORLD_WITH_TYPES'>
// expected: 'helloWorldWithTypes'Solution
type CamelCase<S extends string> =
S extends `${infer Head}_${infer C}${infer Tail}`
? `${Lowercase<Head>}${Uppercase<C>}${CamelCase<Tail>}`
: Lowercase<S>The key idea: split at each underscore into three parts — lowercase what's before it, uppercase the character right after it, and recurse on the rest.
Breaking it down
Step 1 — Three-part pattern match at underscore
S extends `${infer Head}_${infer C}${infer Tail}`Template literal inference splits the string at the first _ into three parts:
Head: everything before the underscoreC: the single character right after the underscoreTail: the rest of the string
For 'hello_world_with_types': Head = 'hello', C = 'w', Tail = 'orld_with_types'
Step 2 — Transform and recurse
`${Lowercase<Head>}${Uppercase<C>}${CamelCase<Tail>}`Lowercase<Head>normalizes the segment (handles all-caps input)Uppercase<C>creates the camelCase capital letterCamelCase<Tail>recursively processes remaining underscores
Step 3 — Base case
: Lowercase<S>When there's no underscore left, just lowercase the remaining segment.
Walkthrough:
S = 'hello_world_with_types'
Head = 'hello', C = 'w', Tail = 'orld_with_types'
→ 'hello' + 'W' + CamelCase<'orld_with_types'>
S = 'orld_with_types'
Head = 'orld', C = 'w', Tail = 'ith_types'
→ 'orld' + 'W' + CamelCase<'ith_types'>
S = 'ith_types'
Head = 'ith', C = 't', Tail = 'ypes'
→ 'ith' + 'T' + CamelCase<'ypes'>
S = 'ypes' (no underscore)
→ 'ypes'
Final: 'hello' + 'W' + 'orld' + 'W' + 'ith' + 'T' + 'ypes'
= 'helloWorldWithTypes' ✓For uppercase input 'HELLO_WORLD_WITH_TYPES':
Head = 'HELLO' → Lowercase → 'hello'
C = 'W' → Uppercase → 'W'
Tail = 'ORLD_WITH_TYPES' → recurse...
→ 'hello' + 'W' + 'orld' + 'W' + 'ith' + 'T' + 'ypes'
= 'helloWorldWithTypes' ✓Deep Dive
Why three-part inference ${Head}_${C}${Tail}?
The critical insight is inferring a single character C right after the underscore. This is more precise than splitting into just two parts:
${Head}_${Tail}— you'd need to separately capitalizeTail's first letter, but thenLowercasein the next recursion destroys that capitalization${Head}_${C}${Tail}—Cis already isolated, justUppercase<C>it directly, andTailnever passes throughLowercaseuntil it's split again
This avoids the common pitfall where recursive Lowercase destroys the Capitalize applied in a previous recursion level.
Why Lowercase<Head> is needed
Without lowercasing, all-caps input like 'HELLO_WORLD' would become 'HELLOWorld' instead of 'helloWorld'. The Lowercase<Head> normalizes each segment before the underscore.
Edge cases
Consecutive underscores: 'foo__bar'
Head = 'foo',C = '_',Tail = 'bar'Uppercase<'_'>='_'(non-letter, unchanged)- Result:
'foo_Bar'— preserves one underscore
Leading underscore: '_foo_bar'
Head = '',C = 'f',Tail = 'oo_bar'- Result:
'' + 'F' + CamelCase<'oo_bar'>→'Foo_bar'→ continues recursing
No underscores: 'hello'
- Falls to base case →
'hello'
Alternative: two-part split approach
A common first attempt uses two-part splitting:
// ❌ Broken: Lowercase destroys previous Capitalize
type CamelCase<S extends string> =
S extends `${infer Head}_${infer Tail}`
? `${Lowercase<Head>}${CamelCase<Capitalize<Lowercase<Tail>>>}`
: Lowercase<S>This fails because when recursing on 'World_with_types', the Lowercase<Head> turns 'World' back to 'world', losing the capitalization applied by the previous level's Capitalize. The three-part split avoids this entirely.
Template literal types are Turing-complete
This challenge showcases that TypeScript's template literal types, combined with recursion and conditional types, form a Turing-complete string processing system. You can implement virtually any string transformation at the type level.
Key Takeaways
- Three-part template inference (
${A}_${B}${C}) lets you isolate a single character for transformation without it being affected by later recursions LowercaseandUppercaseare built-in intrinsic types — use them for case transformation- Recursive template literal types can process strings character-by-character or segment-by-segment
- Watch out for recursive destruction — a common bug where transformations from one level get undone by the next level's processing
