Typescript says array.pop() might return undefined even when array is guaranteed to contain elements

See microsoft/TypeScript#30406 for a canonical answer.


For the question, “why does the compiler think that pop() might return undefined even when I check the array’s length“, the short answer is “because the TypeScript standard library’s call signature for the pop() method on Array<T> returns T | undefined“:

interface Array<T> {
  // ... elided
  pop(): T | undefined;
  // ... elided
}

Thus whenever you call pop() on an array, the type of the return value will include undefined.


The logical next question is “why don’t they make better call signatures which return T if the length is nonzero and undefined if the length is zero?” The answer to that is “because checking the length property of a general array type doesn’t change the apparent type of the array, so the call signatures can’t tell the difference”.

You could, for example, add a few call signatures like this:

interface Array<T> {
  pop(this: { length: 0 }): undefined;
  pop(this: { 0: T }): T;
}

By using this parameters, each call signature will only be selected when the array on which the method is called matches the specified type. If the array has a length of 0, return undefined. If the array has an element of type T at the key 0, return T.

This will work well for tuple types whose length is fixed and whose element indices are known:

declare const u: [];
const a = u.pop(); // undefined

declare const v: [1, 2];
const b = v.pop(); // 1 | 2

But of course calling pop() on a tuple is a bad idea and not the kind of thing you’d be likely to want. If you have a variable-length array and call pop() on it, neither call signature is selected and you fall back to the built-in T | undefined, even if you try to check the length:

const w = Math.random() < 0.5 ? [] : ["a"] // string[]
if (w.length) {
  w.pop().toUpperCase(); // error! Object is possibly undefined
}

The problem is that the length property of an Array<T> is of type number, and there’s no “nonzero number” type to narrow w.length to. In order to support this kind of thing, you’d need something like negated types which are not part of TypeScript. It’s possible that with sufficient compiler work, someone could give enough structure to arrays in TypeScript that a truthiness check on w.length would narrow down the type of the array to something you can call pop() on without worrying about getting undefined out.

But it would add a ton of complexity to the compiler and the language to support this use case. The benefit is not likely to outweigh the cost.


In the absence of this, it’s so much easier for you to skip the length check and just call pop(), checking to see whether it’s undefined or not. This is the same amount of work for you, since it’s just moving the check from before to after the call, and it makes things more straightforward for the compiler.

The other answers posted here suggest this and other workarounds, so I won’t go into them. My main point is that the language is just not equipped to allow a length check to affect the behavior of pop(). Or, as @RyanCavanaugh (dev lead of TS) commeted in microsoft/TypeScript#30406,

This isn’t something we’re capable of tracking

Oh well!

Playground link to code

Leave a Comment