#34286β€’Hard

Take Elements

Take<N, Arr> extracts the first N elements of a tuple, or the last N when N is negative. Accumulator counting and template literal sign detection do the work.

Take<N, Arr> returns the first N elements from a tuple Arr. If N is negative, it returns the last |N| elements instead. The type system has no arithmetic and no slice, so you count with accumulator tuples and detect the sign of a number through template literal inference. Both techniques show up constantly in type-level array problems.

For example,

type T0 = Take<2, [1, 2, 3]> // [1, 2]
type T1 = Take<3, ['1', 2, true, false]> // ['1', 2, true]
type T2 = Take<-2, [1, 2, 3]> // [2, 3]
type T3 = Take<0, [1, 2, 3]> // []
type T4 = Take<5, [1, 2, 3]> // [1, 2, 3]
type T5 = Take<3, []> // []

Challenge Instructions: Take Elements

Hard

Implement a type Take<N, Arr> that returns the first N elements from an array Arr. If N is negative, return the last |N| elements

For example,

type T0 = Take<2, [1, 2, 3]> // [1, 2]
type T1 = Take<3, ['1', 2, true, false]> // ['1', 2, true]
type T2 = Take<-2, [1, 2, 3]> // [2, 3]
type T3 = Take<0, [1, 2, 3]> // []
type T4 = Take<5, [1, 2, 3]> // [1, 2, 3]
type T5 = Take<3, []> // []

View on GitHub: https://tsch.js.org/34286

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.

Loading...

Detailed Explanation

The solution, all three types:

type TakeFirst<
  N extends number,
  Arr extends unknown[],
  Acc extends unknown[] = [],
> = Acc['length'] extends N
  ? Acc
  : Arr extends [infer Head, ...infer Rest]
    ? TakeFirst<N, Rest, [...Acc, Head]>
    : Acc
 
type TakeLast<
  N extends number,
  Arr extends unknown[],
  Acc extends unknown[] = [],
> = Acc['length'] extends N
  ? Acc
  : Arr extends [...infer Rest, infer Last]
    ? TakeLast<N, Rest, [Last, ...Acc]>
    : Acc
 
type Take<N extends number, Arr extends unknown[]> =
  `${N}` extends `-${infer P extends number}`
    ? TakeLast<P, Arr>
    : TakeFirst<N, Arr>

The problem splits into taking from the front, taking from the back, and deciding which of the two applies. Start with the decision.

Detecting a negative number

Types can't do N < 0, but they can look at how a number prints. Interpolating a numeric literal into a template literal turns it into a string literal: `${-2}` is '-2'. So the check

[object Object]

asks: does N start with a minus sign when written out? If it does, infer P extends number captures the digits after the minus and converts them back into a number. For N = -2, P becomes 2. That extends number inside an infer clause is a TypeScript 4.8+ feature; without it P would stay a string like '2', which is useless for counting. This one line gives us both the sign check and the absolute value in a single pattern match.

Counting without arithmetic: the accumulator

TakeFirst needs to stop after exactly N elements, but there's no N - 1 in the type system. The standard trick is to grow a tuple and read its 'length':

Watch it run for Take<2, [1, 2, 3]>:

// TakeFirst<2, [1, 2, 3], []>       β†’ length 0, keep going
// TakeFirst<2, [2, 3],    [1]>      β†’ length 1, keep going
// TakeFirst<2, [3],       [1, 2]>   β†’ length 2 extends 2 β†’ return [1, 2]

The second exit condition is just as important: when Arr no longer matches [infer Head, ...infer Rest], the input is exhausted and we return whatever we have. That's what makes Take<5, [1, 2, 3]> gracefully return [1, 2, 3] and Take<3, []> return [] instead of recursing forever.

Taking from the back

TakeLast is the mirror image. Variadic tuple types let you match from the end just as easily as from the front: [...infer Rest, infer Last] peels off the final element. Each peeled element is prepended to the accumulator, [Last, ...Acc], so the original order is preserved. For Take<-2, [1, 2, 3]>:

// TakeLast<2, [1, 2, 3], []>     β†’ length 0 β‰  2 β†’ peel 3 β†’ Acc = [3]
// TakeLast<2, [1, 2],    [3]>    β†’ length 1 β‰  2 β†’ peel 2 β†’ Acc = [2, 3]
// TakeLast<2, [1],       [2, 3]> β†’ length 2 extends 2 β†’ return [2, 3]

If we had appended instead of prepended, the result would come out reversed as [3, 2], an easy mistake to make in these recursions.

Edge cases the tests cover

The pattern of "recurse while growing an accumulator, compare its 'length' to stop" is the type-level equivalent of a counting loop. Once you've written it here, you'll recognize it in dozens of other tuple challenges.

This challenge is originally from here.

Share this challenge

Learn the Concepts