Check whether a string or number reads the same backwards, entirely in types. A recursive template-literal split does the reversal.
IsPalindrome<121> asks the compiler to reverse a number's digits, so first you teach it to reverse a string.
The task: check whether a string or number reads the same forwards and backwards. Along the way you'll pick up two techniques you'll reuse constantly: recursively reversing a string at the type level, and using template literals to treat numbers and strings uniformly, the same trick that powers type-safe route parsers and key transformers.
For example:
IsPalindrome<'abc'> // false
IsPalindrome<121> // trueImplement type IsPalindrome<T> to check whether a string or number is palindrome.
For example:
IsPalindrome<'abc'> // false
IsPalindrome<121> // trueView on GitHub: https://tsch.js.org/4037
Change the following code to make the test cases pass (no type check errors).
π Lifetime-License is leaving on August 10, 2026
Get it now for $29
One-time payment. Lifetime access to all pro challenges.
Both pieces of the solution up front:
type ReverseString<S extends string> = S extends `${infer First}${infer Rest}`
? `${ReverseString<Rest>}${First}`
: ''
type IsPalindrome<T extends string | number> =
`${T}` extends ReverseString<`${T}`> ? true : falseThe runtime algorithm you'd write in JavaScript (reverse the input, compare it to the original) translates almost word for word into the type system.
The input can be a string or a number (121, 2332, ...), but string reversal only makes sense on strings. Wrapping the type parameter in a template literal, `${T}`, stringifies it: `${121}` evaluates to the string literal type '121', while a string like 'abc' passes through unchanged. Doing this once at the boundary means the rest of the solution only ever deals with strings, with no duplicated logic for the number case.
ReverseString uses the classic recursive split:
`${infer First}${infer Rest}` relies on a special rule of template literal inference: when two infer placeholders are adjacent, the first one matches exactly one character and the second swallows everything else.`${ReverseString<Rest>}${First}`.'', the base case.Tracing a small input makes the flow clear:
ReverseString<'abc'>
// β `${ReverseString<'bc'>}a`
// β `${`${ReverseString<'c'>}b`}a`
// β `${`${`${ReverseString<''>}c`}b`}a`
// β 'cba'Note that this recursion is not tail-recursive. The outer template literal still has work to do after the recursive call returns, so it hits TypeScript's depth limit at roughly 45β50 characters. For this challenge's short inputs that's plenty; if you ever need to reverse huge strings, you'd reach for an accumulator, the same way the Length of String challenges do.
With reversal in hand, the check is a single conditional:
[object Object]A string literal type extends another only when they're identical. So:
IsPalindrome<'abba'>: reversed is 'abba', identical, so extends holds β true.IsPalindrome<'abc'>: reversed is 'cba', and 'abc' extends 'cba' fails β false.IsPalindrome<121>: stringified to '121', reversed is '121' β true.IsPalindrome<'b'> β true: a single character reverses to itself.IsPalindrome<'abca'> β false: the first and last characters match, but the reversal 'acba' differs in the middle. Comparing whole strings catches this without any index bookkeeping.IsPalindrome<2332> β true and IsPalindrome<19260817> β false: multi-digit numbers work exactly like strings once stringified.Reverse-and-compare is a reminder that many type-level puzzles don't need clever tricks, just a faithful translation of the runtime algorithm into recursive conditional types.
This challenge is originally from here.