FilterOut<T, F> removes tuple elements assignable to F. The catch is never, which makes naked conditional checks silently evaporate.
A tuple filter sounds easy until the test suite hands you never.
FilterOut<T, F> removes every element assignable to F from the tuple T. The recursion itself is friendly, but the tests are built around the type that breaks naive solutions: never. To pass, you need to understand why conditional types silently disappear when handed never, and how wrapping both sides in a tuple switches that behavior off.
For example,
[object Object]Implement a type FilterOut<T, F> that filters out items of the given type F from the tuple T.
For example,
[object Object]View on GitHub: https://tsch.js.org/399
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.
The solution first:
type FilterOut<T extends any[], F> = T extends [infer First, ...infer Rest]
? [First] extends [F]
? FilterOut<Rest, F>
: [First, ...FilterOut<Rest, F>]
: []Five lines, one of which contains the entire difficulty of the challenge.
T extends [infer First, ...infer Rest] splits the tuple into its head and everything after it, the type-level equivalent of const [first, ...rest] = arr:
// T = [1, never, 'a']
// First = 1
// Rest = [never, 'a']Each step makes one keep-or-drop decision about First and recurses on Rest:
F, so the result is just FilterOut<Rest, F> and the element never appears.[First, ...FilterOut<Rest, F>], prepending the element onto the filtered remainder with a spread.When the tuple is empty, [] doesn't match the head/tail pattern and the : [] base case ends the recursion. That also directly answers the first test: FilterOut<[], never> is [].
First extends F?This is where naive solutions fail. The obvious membership check would be:
[object Object]But conditional types have a special rule: when the type left of extends is a naked type parameter and you instantiate it with never, the conditional doesn't evaluate either branch. It short-circuits to never itself. (This is a side effect of distributivity: never is the empty union, so there's nothing to distribute over, and the result is empty.)
You can watch the damage in miniature:
type IsNever1<X> = X extends never ? true : false
type A = IsNever1<never> // never (not true)
type IsNever2<X> = [X] extends [never] ? true : false
type B = IsNever2<never> // trueWith the naked check, the moment First is never the whole keep-or-drop decision evaporates into never, and your output tuple silently corrupts. Since four of the six tests filter on never, this is what the challenge is probing.
[First] extends [F] wraps both sides in a one-element tuple. Now the thing left of extends is [First], a tuple type rather than a naked type parameter, so distributivity is disabled and the comparison happens normally. [never] extends [never] is an honest structural check that evaluates to true, so never elements are correctly dropped:
// FilterOut<['a', never], never>
// step 1: ['a'] extends [never]? no β ['a', ...FilterOut<[never], never>]
// step 2: [never] extends [never]? yes β FilterOut<[], never>
// step 3: base case β []
// result: ['a']The last case deserves a close look:
FilterOut<[number | null | undefined, never], never | null | undefined>
// expected: [number | null | undefined]The first element is a union that partially overlaps F. It should not be removed, and the tuple wrapping gives you that for free, in a second way. Because [First] extends [F] is non-distributive, the union is checked as a whole: number | null | undefined is only assignable to never | null | undefined if the number part fits too. It doesn't, so the whole element is kept intact. A distributive check would have split the union and tried to filter it piece by piece, which is not what a tuple filter should do. Elements are atomic; either the entire element matches F or it stays.
FilterOut<[], never> β []: the base case, no recursion at all.FilterOut<[never], never> β [] and FilterOut<[1, never, 'a'], never> β [1, 'a']: never elements are detected and dropped, in any position.F as a union (never | null | undefined): each element is compared against the whole union; undefined and null go, false stays.F without being fully contained survives unchanged.The takeaway pattern: any time a type-level check must treat never (or unions) as a single value rather than distributing, wrap both sides in [...]. It's one of the most frequently needed tricks in the hard tier.
This challenge is originally from here.