Mastering TypeScript: Implementing Push, Pop, Shift, and Unshift in Tuples
Tags: typescript, javascript, webdev, beginners
Published: 2024-06-18 02:05:50

## Introduction

<img width="100%" style="width:100%" src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExN2ludHpiMHJoZ3h6dTB0bXl0dHFqb2hsaW53aDY4dzc1dGFoZGFpMyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/l3fZLMbuCOqJ82gec/giphy.gif" alt="A wild thought image">

So, here's a wild thought! What if I told you that we can use JS array functions like `pop`, `push`, `shift`, `unshift` with Typescript tuple type? You might be like, why…why would we even need that? Isn't TS already hard enough? Who on earth would want to make it even more complex?

Look, I did warn you that this is a bit out there. I've kinda become a Typescript fanatic recently and I love digging into some bizarre TS stuff. Plus, it's not just for kicks. It also helps us understand a few crucial TS concepts that can come in handy when crafting your own utilities.

So let me grab your attention and share with you what we are gonna be doing. 

> 💡 NOTE: This blog post is totally inspired by Typescript challenges here: https://github.com/type-challenges/type-challenges?tab=readme-ov-file The utilities mentioned in this blog were part of these challenges.


## What are we gonna do

Its simple we are building typescript utility generics that does same operations as functions of an Array like push pop etc.

Here are some examples,

```tsx
type Pop<T extends unknown[]> = T extends [...infer H, unknown] ? H : []
type Shift<T extends unknown[]> = T extends [unknown, ...infer T] ? T : []
type Push<T extends unknown[], I extends unknown> = [...T, I]
type Unshift<T extends unknown[], I extends unknown> = [I, ...T] 

type popRes = Pop<[3, 2, 1]>
type shiftRes = Shift<[3, 2, 1]>
type pushRes = Push<[3,2,1], 0>
type unshiftRes = Unshift<[3,2,1], 4>
```

## Getting Started

<img width="100%" style="width:100%" src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExanU0eHM3MmZhM3hqc3h0Nm9kYmN2NmxhY3psb3VjZWY0aWoyYTV5dCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/Yy6GhtIk8l76u8nlIF/giphy.gif" alt="Let us get started image">


Before we dive into the implementation of these utilities we first need to understand couple of basic things in TypeScript:

### 1. Tuple Types

A tuple type is similar to an `Array` type which has known types at the specified index position. A typical example of tuple would like below:

```tsx
type TupleType = [number, boolean];
```

Here `TupleType` is a tuple type with `number` type being at position `0` and `boolean` type at position `1`. Analogous to Javascript arrays, if you try to index tuple type out of its bounds we will get an error

```tsx
type res = TupleType[2] // <------ Tuple type 'TupleType' of length '2' has no element at index '2'
```

For more information on typescript tuple type read its documentation here: [TS Tuple Types](https://www.typescriptlang.org/docs/handbook/2/objects.html#tuple-types)

### 2. Variadic Tuples

Variadic tuples were introduced in Typescript via this [PR](https://github.com/microsoft/TypeScript/pull/39094). According to the PR, variadic tuples are:

> *The ability for tuple types to have spreads of generic types that can be replaced with actual elements through type instantiation*
> 

Some examples of variadic tuples are as follows:

```tsx
type WrapWithStringAndBool<T extends Array<unknown>> = [string, ...T, boolean];

type res = WrapWithStringAndBool<[1,2,3,4]> // <-- [string, 1, 2, 3, 4, boolean]
```

If you checkout the above example, we have `WrapWithStringAndBool` generic that accepts an array of unknowns and returns a tuple with `string` and `boolean` tupes at the first and the last position in the tuple.

Here the `...T` is the variadic element which acts as a placeholder for values from the input `T`.

Variadic tuples has a lot to offer and is a great piece of feature. I would like recommend to go through its rules and examples mentioned in this [PR](https://github.com/microsoft/TypeScript/pull/39094).

### 3. Conditional Types

Every developer knows the `if` `else` block that helps you to write conditional code. In the similar way, Typescript conditional types take the following format: `condition ? trueBranch : falseBranch`. This is very much similar to the [ternary operator in JS](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_operator).

For a type to become conditional, the `condition` part should consist of `extends` keyword:

```tsx
Type extends OtherType ? trueBranch : falseBranch
```

Were `Type` if matches with `OtherType` then `trueBranch` is executed or else the `falseBranch`. 

Conditional Types can also be used to narrow the types to provide specific types. Consider the following example from [Typescript cheatsheet](https://www.typescriptlang.org/cheatsheets/):

```tsx
type Bird = { legs: 2 };
type Dog = { legs: 4 };
type Ant = { legs: 6 };
type Wolf = { legs: 4 };

type HasFourLegs<Animal> = Animal extends { legs: 4 } ? Animal : never;

type Animals = Bird | Dog | Ant | Wolf;

type FourLegs = HasFourLegs<Animals>; // <--- Dog | Wolf
```

Here we have 4 types `Bird` `Dog` `Ant` and `Wolf` with their respective legs property. Next, we have built `HasFourLegs`. It does the following thing:

- It compares `Animal` object with `{ legs: 4 }` object with the help of `extends` keyword.
- If they are equal, `Animal` is returned or else nothing is returned.

In the last line we make use of `HasFourLegs` by passing it the `Animals` union type. A thing to note here is that, when an Union type is provided to the left side of the `extends` keyword then all the values of Union are distributed such that each value is tested against the object `{ legs: 4 }`. You can read more about the distributive nature of unions over [here](https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types).

### 4. Inferring Within Conditional Types

Conditional types can also be used to infer types when we do comparison with the help of `infer` keyword. For example, consider the TS utility [ReturnType](https://www.typescriptlang.org/docs/handbook/utility-types.html#returntypetype). This utility returns the return type of the function that is being passed to it. Internally it works in the following ways:

```tsx
type GetReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer R ? R : never;

type res = GetReturnType<() => number> // <--- number
```

In the `trueBranch` of the above example if you closely observe that we make use of `infer R`. Here we tell TS like while comparing if `T` matches the format to the right of `extends` then place the return type of function in `R`.

When we pass `() => number` to the `GetReturnType` a number is returned since it matches the format to the right of `extends` keyword in the generic.

Now that we are clear with our basics, let us start with writing our first utility. `Pop`

## Pop

<img width="100%" style="width:100%" src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExZmg1YTlhYWN4eTdua2x5OXdibmQ4OTRyMDc1YXYzbDd0bzZmN2hhbSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/vN2VlOnSeLd6UJsdRx/giphy.gif" alt="Pop in TS image">


In Javascript a `pop` function would remove the last element of an array. To build this in Typescript, we do the following:

```tsx
type Pop<T extends unknown[]> = T extends [...infer H, unknown] ? H : []
```

Let me example what is happening here:

- We tell TS that `Pop` is going to be our generic with parameter `T` which can be an array of `unknown`s.
- Next, with the help of this: `T extends [...infer H, unknown]` we tell TS that the first N-1 elements of the tuple/array T should be inferred with H and the Nth i.e. the last element should be inferred as unknown.
- If the Tuple T matches this pattern then `H` is returned or else we return an empty array.

## Shift

<img width="100%" style="width:100%" src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExaDExcGF6cndpYm5zczFzeXI2OHJqM3N2bzAxdWVzZ29obTEwdmJ4eCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/xTiTnu8nhFODl7Fvdm/giphy.gif" alt="Shift in TS image">

Javascript array’s has another built-in function that helps you to remove its first element. This is an in-place operation that affects the original array. The returned value by this function is an array’s first element. Here is a quick look of this function:

```tsx
let x = [1, 2, 3, 4];
const firstElement = x.shift();

firstElement // <----- 1
x // <----- [2, 3, 4]
```

Similar to `shift` function, we can create a generic utility in Typescript that removes the first element from a tuple type. Here is how it’s the utility will look like:

```tsx
type Shift<T extends unknown[]> = T extends [unknown, ...infer R] ? R : []
```

The `Shift` utility here expects it’s input to be an tuple. In this case, since we don’t exactly know the types inside the tuple hence we extend input `T` as `unkown[]`

To the right hand side, we have:

- We are making using of conditional types, were we check if `T` matches the following pattern `[unknown, ...infer R]`.
    - We can also identify the things that we learned in the getting started section.
    - We make use of `extends` keyword to create a conditional types.
    - We also make use of variadic tuple types, where in the right side tuple of `extends` we have a inferential variadic element `R`
- If the condition is met we return `R` which is the remaining element of the tuple.

## Push & Unshift

<img width="100%" style="width:100%" src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExdGx6ZGJ2eTcwcmk0aDl4ZnZnNTByeDAwcjlqN3pobXU2OXZrd2xseiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/VfNFqPmYorydG/giphy-downsized.gif" alt="Shift in TS image">

Similar to Javascript’s push built-in function that appends a new element to the array, we can also create a similar utility:

```tsx
type Push<T extends unknown[], I extends unknown> = [...T, I]
```

This is one of the simplest utility from all. The key aspect of this Utility is that, 

- We make use of destructing of tuple concept with `...T`  inside a new tuple.
- To this tuple at the end we add `I`
- Together this utility would return a new tuple type that combines the elements from `T` and adds `I` at the end.

Here is the result for the same:

```tsx
type res = Push<[1,2,3], 4>

res // <----- [1, 2, 3, 4]
```

Similar to this we have `Unshift` as well:

```tsx
type Unshift<T extends unknown[], I extends unknown> = [I, ...T] 
```

## Summary

In this blog post, we covered:

- Tuple Types
- Conditional Types
- Variadic Tuple Types
- Conditionally Inferred Types

Lastly, we explored the `Push`, `Pop`, `Shift`, and `Unshift` generic utilities in TypeScript, which are analogous to their JavaScript counterparts.

Thanks a lot for reading my blogpost.

You can follow me on [twitter](https://twitter.com/keurplkar), [github](http://github.com/keyurparalkar), and [linkedIn](https://www.linkedin.com/in/keyur-paralkar-494415107/).