Absolute
Absolute
Challenge
Implement the Absolute type. A type that takes a string, number, or bigint. The output should be a positive number string.
type Test = -100
type Result = Absolute<Test> // expected to be "100"Solution
type Absolute<T extends number | string | bigint> =
`${T}` extends `-${infer N}` ? N : `${T}`The entire trick fits in one line: convert to a string, then strip a leading minus sign if present.
Step-by-step breakdown
Step 1 — Coerce everything to a string with a template literal
`${T}`TypeScript's template literal types can interpolate number, string, and bigint directly, converting their literal types into string literal types:
type A = `${-100}` // "-100"
type B = `${100}` // "100"
type C = `${-42n}` // "-42"
type D = `${"-100"}` // "-100"This single coercion handles all three accepted input types uniformly.
Step 2 — Pattern-match against a leading minus
`${T}` extends `-${infer N}` ? N : `${T}`- If the string starts with
-, TypeScript infers the remainder asN(everything after the dash) and returns it — effectively stripping the negative sign. - If it doesn't start with
-, the value is already non-negative, so we return\${T}`` unchanged.
All test cases walk-through
| Input | \${T}`` | Matches -${N}? | Result |
|---|---|---|---|
-100 | "-100" | ✅ N = "100" | "100" |
100 | "100" | ❌ | "100" |
"-100" | "-100" | ✅ N = "100" | "100" |
"100" | "100" | ❌ | "100" |
-42n | "-42" | ✅ N = "42" | "42" |
0 | "0" | ❌ | "0" |
Deep Dive
Why does the output type have to be a string?
You might expect Absolute<-100> to return the number 100, not the string "100". The reason is TypeScript's type system: while you can do arithmetic on numeric literal types in limited ways (e.g., tuple length tricks), stripping a sign character is inherently a string operation. The template literal infer pattern \-${infer N}`` can only give back a string.
If you truly needed a numeric return type you'd have to convert the resulting string back to a number, which requires the tuple-length trick and breaks for large numbers — not worth the complexity here.
Template literals as a string processing tool
This challenge demonstrates that template literal types are more than just string concatenation — they're a lightweight string parsing mechanism:
// Extract path segments
type Head<S extends string> = S extends `${infer H}/${string}` ? H : S
// Check prefix
type StartsWithA<S extends string> = S extends `A${string}` ? true : false
// Strip leading character
type StripMinus<S extends string> = S extends `-${infer R}` ? R : SThe infer keyword inside a template literal pattern is the key enabler.
Constraint: T extends number | string | bigint
Why include bigint? Because JavaScript's BigInt literals (like -42n) are their own primitive type in TypeScript. Without the bigint branch, you'd get a type error if someone passed a bigint. Template literal interpolation supports all three, so the constraint matches the capability.
What about "-0"?
Negative zero (-0) is a quirky JavaScript value. In TypeScript's type system, -0 as a numeric literal is typically treated as 0, so Absolute<-0> gives "0" — which is correct.
Why not use Math.abs-style conditional on number sign?
You could try a numeric conditional approach using conditional distribution, but TypeScript's numeric type arithmetic is very limited. You can't write T > 0 ? T : -T at the type level for arbitrary numbers. The string-based approach is the only clean solution.
Key Takeaways
- Template literals coerce
number,string, andbigintto their string equivalent at the type level — a powerful unification trick \-${infer N}`` pattern is the canonical way to strip (or detect) a leading minus sign in a type-level string- The output is always a string because sign-stripping is a string operation; converting back to a numeric type would require complex tuple arithmetic
- This challenge is a great introduction to template literal types as a parsing tool, not just a concatenation tool
- The solution works uniformly across all three input variants (
number,string,bigint) thanks to the initial coercion step
中文解析
类型定义解读
type Absolute<T extends number | string | bigint> =
`${T}` extends `-${infer N}` // 先用模板字面量把 T 统一转为字符串,再匹配前导负号
? N // 有负号 → 返回负号后面的部分(即绝对值字符串)
: `${T}` // 无负号 → 原值即为绝对值,直接返回字符串形式逐步分析
Step 1:模板字面量类型的强制字符串转换
`${-100}` // "-100"(string literal type)
`${100}` // "100"
`${-42n}` // "-42"
`${"-100"}` // "-100"(string 原样保留)TypeScript 的模板字面量类型可以内插 number、string、bigint,统一产出字符串字面量类型。这一步让三种不同的输入类型走同一条处理路径。
Step 2:用 infer 模式匹配剥离负号
"-100" extends `-${infer N}` → N = "100" ✅
"100" extends `-${infer N}` → 不匹配 ❌ → 原值返回-${infer N} 是"以 - 开头的字符串"的类型模式,infer N 捕获减号之后的所有字符。
各测试用例走查
| 输入 | \${T}`` | 匹配负号? | 结果 |
|---|---|---|---|
-100 | "-100" | ✅ N="100" | "100" |
100 | "100" | ❌ | "100" |
"-100" | "-100" | ✅ N="100" | "100" |
"100" | "100" | ❌ | "100" |
-42n | "-42" | ✅ N="42" | "42" |
0 | "0" | ❌ | "0" |
为什么输出是字符串而不是数字?
TypeScript 的类型系统不支持任意数值的算术操作(无法写 -T 或 Math.abs<T>)。
符号剥离是字符串操作,infer 捕获的结果天然是字符串类型。
如果要转回数字类型,需要借助"元组长度技巧",对大数无效且极其复杂——不如就接受字符串输出。
考察知识点
- 模板字面量类型作为类型级字符串处理工具:不只是字符串拼接,
infer配合模板模式可以做"前缀匹配 + 捕获剩余",实现轻量级字符串解析 - **
\${T}`统一类型转换**:将number | string | bigint` 三种类型统一为字符串字面量类型,是一种常见的类型归一化技巧 - 模板字面量中的
infer:\-${infer N}`中的infer与条件类型中的infer` 原理相同,只是作用于字符串模式匹配 - 约束选择:
T extends number | string | bigint的三路约束与模板字面量支持的三种内插类型一一对应,体现了"约束即文档"的设计思路 - 绕过数值算术限制:TypeScript 的类型系统对数值运算支持极有限,遇到"数值变换"问题时,转成字符串处理往往是最简洁的出路
