Skip to content
webtype.org#231 camel-case · par 4

No. 231 · August 8, 2026 · Hard

CamelCase

Implement `CamelCase<S>` so it converts a snake_case string type to camelCase. A string with no underscore is returned unchanged.

01

Try the puzzle yourself

Par 4

Puzzle

camel-case.ts
Stroke 1 of 4Not run yet

Replace ??? — your solution is checked against the cases below. Tab indents; press Escape then Tab to move focus out.

Checks

4
  • CamelCase<'foo_bar'>
    'fooBar'
  • CamelCase<'foo_bar_baz'>
    'fooBarBaz'
  • CamelCase<'single'>
    'single'
  • CamelCase<'a_b_c'>
    'aBC'

How a check is judged Exact type equality, not assignability — an intersection is not the same as the flattened object.

How everyone did

Fewer than 5 people have solved this one so far. The distribution appears once there is enough of a sample to mean anything.

Archive
02

Annotated solution

Published August 9, 2026

The solution

type CamelCase<S extends string> =
  S extends `${infer H}_${infer T}` ? `${H}${CamelCase<Capitalize<T>>}` : S

The common wrong answer

type CamelCase<S extends string> =
  S extends `${infer H}_${infer T}` ? `${H}${CamelCase<T>}` : S

The underscore is removed but nothing is capitalised, so `"foo_bar"` becomes `"foobar"`. Deleting the separator is only half the transformation — the case change is the other half.

Line by line

  1. Capitalize<T>

    One of four intrinsic string types the compiler implements natively — `Uppercase`, `Lowercase`, `Capitalize`, `Uncapitalize`. They have no TypeScript source; they are built into the checker.

  2. CamelCase<Capitalize<T>>

    Capitalising *before* recursing means each level only has to handle the underscore nearest the front. `"a_b_c"` therefore yields `"aBC"` — the recursion capitalises each remaining segment in turn.

Takeaway

The intrinsic case types make string manipulation at the type level far less painful than it has any right to be. Reach for them before writing a character-by-character recursion.