Manipulate Arrays with pop() TypeScript

I’m going over some of the basic FCC JS challenges in TypeScript to make sure I have a good sense of the errors I’m likely to get.

I’m getting the error
Type '(string | number)[] | undefined' is not assignable to type '(string | number)[]'. Type 'undefined' is not assignable to type '(string | number)[]'.ts(2322)

but when I compile the code and run it in node I get

[ [ 'John', 23 ], [ 'cat', 2 ], [ 'dog', 3 ] ]
[ 'dog', 3 ]
[ [ 'John', 23 ], [ 'cat', 2 ] ]

It seems as though pop() is doing it’s job, but I’m getting an error. Just wanted to get your thoughts on why that is. Code is below.

// Manipulate Arrays With push()
const menagerie: ((string | number) []) [] = 
[
  ["John", 23],
  ["cat", 2]
]
menagerie.push(["dog", 3])
console.log(menagerie);

// Manipulate Arrays With pop()
const pet: (string | number)[] = menagerie.pop()

console.log(pet);
console.log(menagerie);

Ahhhh… Found it

let pet: (string | number)[] = menagerie.pop() as (string | number)[]

When the array is empty then pop() returns undefined. You’re basically telling TypeScript to ignore that, which is probably not the best solution. That said, I don’t really understand the point to adding types to your variable declarations instead of letting TS infer them.

1 Like

Thanks Colin,

So by letting TS infer… would TS help to point out that “hey there is a possibility that you could pop off an element from an empty array and that it would return undefined”…

In this instance the best syntax is just regular JS?

In this instance I would say so. If you didn’t explicitly add any types, then pet would have an inferred type of (string | number)[] | undefined.

Theoretically later on, before using pet somewhere else in the code, you would check to make sure it’s defined. This is called “type narrowing” because we’re reducing the possible types that a variable could hold.

if (!pet) {
  // throw an error or return from the function or whatever
}

// any code below here,
// pet's inferred type will be (string | number)[]
1 Like

Super helpful, thanks Colin.

What you’ve also got to bear in mind is that what you’re applying this to is very basic functionality that IRL is absolutely not going to exist on its own.

One of the things a statically typed language like TS is going to push you to do (kinda the entire point) is think a bit about the actual purpose of what you’re doing up-front (rather than it just being some random code).

In this [admittedly very much hypothetical and contrived] situation, you have:

  • a “menagerie”, which is an array of
  • “pets”, which are a two-element array of
  • a type/name (?? I’ll say type) which is a string and
  • an age which is a number
type Pet = [string, number]

Or

type Pet = [petType: string, age: number]

(both exactly the same but you’ll get better type inference with the second version)

So going back up from pets:

type Menagerie = Pet[]

And you can add and take pets from the menagerie.

const menagerie: Menagerie = []

menagerie.push(["cat", 13])
menagerie.pop()

However IRL you aren’t just going to do that blindly, which is what you’re doing here.

What you’re looking at is a very basic introduction to JS, and it’s just demonstrating what pop and push are, so that’s fine, but critically, with regards the original question: you can’t take a pet from the menagerie if the menagerie is empty, it makes zero sense. Yes, pop returns undefined on an empty array, but IMO you have to treat things realistically, so in what situation would would you be running pop on that array? That’s what you have to think about. IRL it’s going to be part of some function, and you’re going to think about how you handle the situation where the “menagerie” is empty

1 Like

Thanks for that message, yeah I feel a bit silly doing this, but it’s kind of helped introduce me to TypeScript in a way where fumbling around on StackOverflow probably wouldn’t have.

I completely agree that it’s all a bit silly and hypothetical.

What I quite like about your example if I’m right, is that there is an inheritance thing happening…

type Pet = [petType: string, age: number]
type Menagerie = Pet
const menagerie: Menagerie =

Is this one of the main benefits of TypeScript and is it commonly cited as why people choose to leverage TS (Of course the main benefit is that it stops bugs). The idea of reusing/extending types, interfaces feels a little oop to me!