String to Number
String to Number
Challenge
Convert a string literal type to a number literal type, behaving like Number.parseInt.
type Result = ToNumber<'123'> // expected: 123Solution
type ToNumber<S extends string> =
S extends `${infer N extends number}` ? N : neverThat's it — one line. TypeScript 4.8 introduced infer with extends constraints in template literal types, which makes this almost trivial.
How it works
S extends `${infer N extends number}` ? N : neverThis tells TypeScript: "try to interpret the string S as a template containing a number. If it can, infer that number as N."
When S = '123', TypeScript recognizes that '123' matches the template `${number}` and infers N = 123 as a numeric literal type.
Walkthrough:
ToNumber<'123'>
→ '123' extends `${infer N extends number}` ? N : never
→ N = 123
→ 123 ✓
ToNumber<'0'>
→ N = 0 ✓
ToNumber<'abc'>
→ 'abc' does not match `${number}`
→ never ✓Deep Dive
Pre-4.8 solution
Before TypeScript 4.8, infer ... extends didn't exist. The classic approach uses a build-up helper:
type ToNumber<S extends string, A extends any[] = []> =
`${A['length']}` extends S
? A['length']
: ToNumber<S, [...A, 1]>This counts up from 0 by growing a tuple A until its length (as a string) matches S. It works but hits recursion limits around ~999.
How the old approach works:
ToNumber<'3'>
A = [] → '0' extends '3'? No
A = [1] → '1' extends '3'? No
A = [1,1] → '2' extends '3'? No
A = [1,1,1] → '3' extends '3'? Yes → A['length'] = 3 ✓Why infer N extends number works
TypeScript 4.8 added the ability to constrain inferred types in conditional types. In template literal position:
`${infer N extends number}`TypeScript's template literal inference engine tries to parse the string as a number. If successful, N is narrowed to the specific numeric literal. This works for:
- Integers:
'42'→42 - Negative numbers:
'-7'→-7 - Decimals:
'3.14'→3.14 - Zero:
'0'→0
What doesn't parse
ToNumber<''> // never (empty string)
ToNumber<'abc'> // never (not a number)
ToNumber<'12px'> // never (unlike parseInt, no partial parsing)
ToNumber<' 42'> // never (leading space)Note that unlike JavaScript's parseInt, the type-level version requires the entire string to be a valid number — no partial parsing.
Also works for other primitives
The same pattern works for booleans and bigints:
type ToBool<S extends string> =
S extends `${infer B extends boolean}` ? B : never
type ToBI<S extends string> =
S extends `${infer N extends bigint}` ? N : never
type T1 = ToBool<'true'> // true
type T2 = ToBool<'false'> // false
type T3 = ToBI<'42'> // 42nNegative numbers and edge cases
type T1 = ToNumber<'-1'> // -1 ✓
type T2 = ToNumber<'0'> // 0 ✓
type T3 = ToNumber<'1e5'> // never (scientific notation not supported)
type T4 = ToNumber<'Infinity'> // neverKey Takeaways
infer N extends numberin template literals (TS 4.8+) is the modern, clean way to parse strings to numbers at the type level- The pre-4.8 tuple-counting approach works for small non-negative integers but hits recursion limits
- Template literal inference with constraints works for
number,boolean,bigint, andstring - Unlike runtime
parseInt, the type-level version requires the full string to be a valid number
