Hone logo
Hone
Problems

TypeScript Exclude Helper Implementation

Many times in TypeScript development, you need to create new types that are subsets of existing union types. This challenge asks you to implement a utility type that can take a union type and remove specific types from it, effectively creating a new, more specific union. This is a common and powerful pattern for type manipulation.

Problem Description

Your task is to implement a generic TypeScript utility type called Exclude<T, U> that:

  • Takes two type parameters:
    • T: The original union type from which you want to exclude elements.
    • U: The type whose members should be excluded from T.
  • Returns a new union type that contains all members of T that are not assignable to U.

In simpler terms, Exclude<T, U> should act like a filter for union types, keeping only those types from T that cannot be assigned to U.

Key Requirements:

  • The implementation must be a generic type.
  • It should correctly handle various types within the union, including primitive types, object types, and null/undefined.
  • The order of types in the resulting union does not matter.

Edge Cases to Consider:

  • What happens when U is never?
  • What happens when T is never?
  • What happens when U is a broader type than T (e.g., excluding string from string | number)?
  • What happens when T is not a union type (e.g., a single type)?

Examples

Example 1:

type T1 = string | number | boolean;
type U1 = number;

type Result1 = Exclude<T1, U1>;
// Expected: string | boolean

Explanation: We start with the union string | number | boolean. We want to exclude number. Since string is not assignable to number and boolean is not assignable to number, both are kept. number is assignable to number, so it is excluded.

Example 2:

type T2 = 'a' | 'b' | 'c' | 'd';
type U2 = 'b' | 'd' | 'e';

type Result2 = Exclude<T2, U2>;
// Expected: 'a' | 'c'

Explanation: From the union 'a' | 'b' | 'c' | 'd', we exclude 'b' and 'd' (as they are directly present and thus assignable). 'a' and 'c' are not assignable to 'b' or 'd', so they remain.

Example 3:

type T3 = string | undefined;
type U3 = undefined;

type Result3 = Exclude<T3, U3>;
// Expected: string

Explanation: We exclude undefined from string | undefined. string is not assignable to undefined, so it remains. undefined is assignable to undefined, so it's removed.

Example 4: (Edge case - U is never)

type T4 = string | number;
type U4 = never;

type Result4 = Exclude<T4, U4>;
// Expected: string | number

Explanation: Excluding never from any type should result in the original type, as nothing is assignable to never.

Constraints

  • The solution must be a generic type definition in TypeScript.
  • No runtime JavaScript code is required, only type-level manipulation.
  • The solution should be efficient at the type level.

Notes

This challenge is a fundamental exercise in understanding TypeScript's conditional types and distributive conditional types. Consider how TypeScript iterates over union types when you use conditional types. A common approach involves using T extends U ? never : T.

Loading editor...
typescript