Hone logo
Hone
Problems

Nullish Coalescing Types in TypeScript

TypeScript's nullish coalescing operator (??) provides a concise way to handle null or undefined values. This challenge focuses on extending this concept to types, allowing you to define types that default to a specific type if the input type is null or undefined. This is particularly useful for optional properties or parameters where you want to ensure a default type is always present.

Problem Description

You are tasked with creating a utility type called NullishCoalesce. This type should take two type arguments, T and U, and return a new type that behaves as follows:

  • If T is null or undefined, the resulting type should be U.
  • Otherwise, the resulting type should be T.

Essentially, NullishCoalesce<T, U> should provide a type-safe way to default to type U when T is nullish. This is analogous to the runtime behavior of the ?? operator, but applied at the type level.

Key Requirements:

  • The utility type must correctly handle both null and undefined as nullish values.
  • The utility type must preserve the original type T when it is not nullish.
  • The utility type should be generic, accepting any valid TypeScript types for T and U.

Expected Behavior:

  • NullishCoalesce<null, string> should resolve to string.
  • NullishCoalesce<undefined, number> should resolve to number.
  • NullishCoalesce<string, number> should resolve to string.
  • NullishCoalesce<number, string> should resolve to number.
  • NullishCoalesce<string | null, number> should resolve to string | number.
  • NullishCoalesce<{ a: string }, { b: number }> should resolve to { a: string }.

Edge Cases to Consider:

  • Types that already include null or undefined (e.g., string | null).
  • Complex types like unions, intersections, and conditional types.
  • Literal types.

Examples

Example 1:

type Result1 = NullishCoalesce<null, string>;
// Result1: string

Explanation: T is null, so the type resolves to U, which is string.

Example 2:

type Result2 = NullishCoalesce<string, number>;
// Result2: string

Explanation: T is string, which is not nullish, so the type resolves to T, which is string.

Example 3:

type Result3 = NullishCoalesce<string | null, number>;
// Result3: string | number

Explanation: T is string | null. Since it's a union including null, the type resolves to string | number.

Example 4:

type Result4 = NullishCoalesce<{ a: string }, { b: number }>;
// Result4: { a: string }

Explanation: T is an object with a property a: string. It's not nullish, so the type resolves to the original object type.

Constraints

  • The solution must be written in TypeScript.
  • The solution must be a valid TypeScript type definition.
  • The solution should be as concise and readable as possible.
  • The solution should handle all the expected behaviors and edge cases described above.

Notes

  • You'll likely need to use conditional types to implement this utility type.
  • Consider how to handle types that already include null or undefined in their definition. You don't want to inadvertently change the type's meaning.
  • Think about how to ensure that the resulting type is as specific as possible while still providing the nullish coalescing behavior.
Loading editor...
typescript