Shift
Shift
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 : neverLet's break it down:
T extends any[]— constrainsTto be an array/tupleT extends [any, ...infer Rest]— pattern-matches: discard the first element, infer the rest asRest? Rest— return the tail: never— if the tuple is empty (no first element to remove), returnnever
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<[]> // neverAlternative: 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 : neverThese 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 : neverThis is the canonical, idiomatic TypeScript answer.
Key Takeaways
- Variadic tuple inference —
T extends [any, ...infer Rest]is the standard pattern to drop the first tuple element inferin tuple position — TypeScript 4.0+ can infer rest elements as a full tuple type- Symmetry with Pop — Shift/Pop are mirror patterns: one takes from the front, one from the back
- Type fidelity — The remaining elements retain their exact positional types
- Empty tuple handling — Decide between
neverand[]based on your semantic needs
