0898 · Includes
0898 · Includes
Problem
Implement a type Includes<T, U> that checks whether U exists in tuple T.
type Result1 = Includes<['a', 'b', 'c'], 'a'> // true
type Result2 = Includes<[1, 2, 3], 4> // falseSolution
type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends
(<T>() => T extends Y ? 1 : 2)
? true
: false
type Includes<T extends readonly unknown[], U> = T extends [
infer First,
...infer Rest,
]
? Equal<First, U> extends true
? true
: Includes<Rest, U>
: falseExplanation
The tricky part is that this challenge wants strict type equality, not loose assignability.
For example:
type A = Includes<[{}], {}> // false in the official testsIf we only used U extends T[number], this case would be wrong because {} is too broad and assignability is not the same as exact equality.
Step by Step
- Split the tuple into
FirstandRest. - Compare
FirstandUwithEqual. - If they are exactly the same type, return
true. - Otherwise, recursively check the remaining elements.
- If the tuple becomes empty, return
false.
Why Equal Is Necessary
Equal<X, Y> is a common utility in type challenges. It checks whether two types are exactly the same instead of just being assignable to each other.
That matters for cases like:
type A = Equal<boolean, false> // false
type B = Equal<true, true> // true
type C = Equal<{}, {}> // trueExample Walkthrough
type Result = Includes<[1, 2, 3], 2>This expands roughly like:
Equal<1, 2> -> false
Includes<[2, 3], 2>
Equal<2, 2> -> trueSo the final result is true.
Alternative Solutions
Option 1: Naive Union Check
type Includes2<T extends readonly unknown[], U> = U extends T[number] ? true : falseThis is short, but not correct for the official tests because it checks assignability, not exact equality.
Option 2: Helper-Based Recursion
type Equal2<X, Y> = (<T>() => T extends X ? 1 : 2) extends
(<T>() => T extends Y ? 1 : 2)
? true
: false
type IncludesHelper<T extends readonly unknown[], U> = T extends [
infer Head,
...infer Tail,
]
? Equal2<Head, U> extends true
? true
: IncludesHelper<Tail, U>
: falseThis is the same algorithm split into smaller units, which can be easier to reuse in later tuple problems.
Thought Process
The first instinct is usually "convert the tuple to a union and check membership." That works for value-level intuition, but it fails at the type level because membership here really means exact type equality.
So the correct direction is:
- Iterate through the tuple one item at a time.
- Compare with a strict equality helper.
- Stop early when a match is found.
This pattern shows up frequently in medium challenges too, so Includes is a good foundation problem.
Key concepts:
- Conditional Types
- Variadic Tuple Types
- Recursive tuple processing
中文解析
类型定义解读
// 严格相等辅助类型:通过"双向条件类型"绕过 TS 的结构类型系统
type Equal<X, Y> =
(<T>() => T extends X ? 1 : 2) extends // 构造一个关于 X 的泛型函数类型
(<T>() => T extends Y ? 1 : 2) // 再构造一个关于 Y 的泛型函数类型
? true // 如果两个函数类型完全一致 → true
: false
type Includes<T extends readonly unknown[], U> =
T extends [infer First, ...infer Rest] // 将元组拆分为「头元素」和「剩余元素」
? Equal<First, U> extends true // 严格比较头元素与目标类型
? true // 命中 → 直接返回 true
: Includes<Rest, U> // 未命中 → 递归检查剩余部分
: false // 元组为空 → 返回 false逐步分析
为什么不能直接用 U extends T[number]?
T[number] 把元组转成联合类型,再用 extends 检查的是"可赋值性"而非"严格相等"。例如:
{} extends {}为 true,但Equal<{}, {}>也是 true——这里没问题boolean extends true | false为 true,但Equal<boolean, true>为 false——这里就出问题了
官方测试中有 Includes<[boolean], false> 期望返回 false,用联合检查会错误地返回 true。
Equal<X, Y> 的工作原理
这是利用 TypeScript 内部"延迟类型求值"的技巧:
<T>() => T extends X ? 1 : 2是一个泛型函数类型,TypeScript 只有在T确定时才能求值。- 两个这样的函数类型仅在
X和Y完全相同时才被认为是同一类型。 boolean和true虽然有赋值关系,但它们的延迟条件展开方式不同,所以Equal<boolean, true> = false。
递归展开示例
Includes<[1, 2, 3], 2>
→ Equal<1, 2> = false → Includes<[2, 3], 2>
→ Equal<2, 2> = true → true ✅考察知识点
- 条件类型(Conditional Types):
T extends U ? A : B的基本形式 infer关键字:在条件类型中提取子类型,[infer First, ...infer Rest]是元组头尾分解的标准模式- 分布式条件类型的陷阱:直接用
extends检查联合类型时会分布展开,导致与预期不符 - 严格类型相等:
Equal<X, Y>利用延迟泛型函数类型实现精确相等判断,是 type-challenges 中的高频工具类型 - 尾递归元组遍历:将问题分解为"处理头元素 + 递归处理剩余",是处理元组的通用模式
