Yuzhe's Blog

yuzhes

Shift

Shift

Challenge Link

Problem

Implement the type version of Array.shift.

type Result = Shift<[3, 2, 1]> // [2, 1]

Solution

Approach

Array.shift removes the first element of an array and returns the remaining elements. In TypeScript’s type system, we can leverage variadic tuple types (introduced in TS 4.0) to destructure a tuple and discard the first element.

The key idea: if we can pattern-match a tuple as [First, ...Rest], then Rest is exactly what we want.

Final Solution

type Shift<T extends any[]> = T extends [any, ...infer Rest] ? Rest : never

Let’s break it down:

  1. T extends any[] — constrains T to be an array/tuple
  2. T extends [any, ...infer Rest] — pattern-matches: discard the first element, infer the rest as Rest
  3. ? Rest — return the tail
  4. : never — if the tuple is empty (no first element to remove), return never

Test Cases

type Result1 = Shift<[3, 2, 1]>       // [2, 1]
type Result2 = Shift<['a', 'b', 'c']> // ['b', 'c']
type Result3 = Shift<[1]>              // []
type Result4 = Shift<[]>               // never

Alternative: Returning [] for empty tuples

Depending on semantics, you might want an empty tuple instead of never for an empty input:

type Shift<T extends any[]> = T extends [any, ...infer Rest] ? Rest : []

This mirrors JavaScript’s runtime behavior where [].shift() returns undefined but the array remains [].

Deep Dive

Variadic Tuple Types

TypeScript 4.0 introduced powerful tuple destructuring via infer in conditional types:

type Head<T extends any[]> = T extends [infer H, ...any[]] ? H : never
type Tail<T extends any[]> = T extends [any, ...infer T] ? T : never

These are the fundamental building blocks for tuple manipulation. Shift is essentially just Tail.

Why [any, ...infer Rest] and not [infer _First, ...infer Rest]?

Both work, but [any, ...] is slightly more explicit that we’re intentionally discarding the first element. Using infer _First would also bind it to a name you never use.

Relationship to Pop

Pop removes the last element: T extends [...infer Rest, any] ? Rest : never

Shift removes the first element: T extends [any, ...infer Rest] ? Rest : never

They are symmetric — same pattern, different ends of the tuple.

Type Preservation

A crucial property: Shift preserves the types of remaining elements with full fidelity.

type T = [string, number, boolean]
type Shifted = Shift<T>  // [number, boolean]

The result is a proper tuple with distinct types per position, not just (string | number | boolean)[].

Playground Verified Solution

The community solution (from the GitHub issue) uses the same infer-based approach:

type Shift<T extends any[]> = T extends [any, ...infer Rest] ? Rest : never

This is the canonical, idiomatic TypeScript answer.

Key Takeaways

  1. Variadic tuple inferenceT extends [any, ...infer Rest] is the standard pattern to drop the first tuple element
  2. infer in tuple position — TypeScript 4.0+ can infer rest elements as a full tuple type
  3. Symmetry with Pop — Shift/Pop are mirror patterns: one takes from the front, one from the back
  4. Type fidelity — The remaining elements retain their exact positional types
  5. Empty tuple handling — Decide between never and [] based on your semantic needs