Repository: TypeScript
Issue: Array.shift() should return defined for consistency with random access
Labels: Suggestion,Awaiting More Feedback

# Suggestion

## 🔍 Search Terms

in:title array shift
https://github.com/Microsoft/TypeScript/issues/13778

## ✅ Viability Checklist

My suggestion meets these guidelines:

* [x] This wouldn't be a breaking change in existing TypeScript/JavaScript code
* [x] This wouldn't change the runtime behavior of existing JavaScript code
* [x] This could be implemented without emitting different JS based on the types of the expressions
* [x] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
* [x] This feature would agree with the rest of [TypeScript's Design Goals](https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals).


## ⭐ Suggestion

Whether an array element is defined/undefined is inconsistent. If you use direct access it's always defined even when the control flow clearly indicates it couldn't possibly be; if you use method-based access it's always possibly undefined even when the control flow clearly indicates it must be defined.

The discussion for random access in [#13778](https://github.com/Microsoft/TypeScript/issues/13778) ended in keeping it `defined` by default and adding a pedantic compiler flag for making it all `undefined`. I'm proposing an extension of that to `shift` and other `Array` methods that should, for consistency, follow the same behaviour.

## 📃 Motivating Example

https://www.typescriptlang.org/play?#code/MYewdgzgLgBAZiEMC8MDaBGANAJiwZiwBYsBWAXQChRJYAjAQwCcUYAKAfQC4YwBXALZ0ApkwCUKAHwwOlauGgwAHqwQgAdBAAWASzhQ2EgPRHlMHRBh8wAE2FwdYYTZjCAbsLAwoWkHwDmWvCIMFoMlsIANsICnlCWdg5OLgx0IB6UjExsSsamUACeAA7CrkxMIExyNIoFqohoAJyN5DAmMHUWMImOzq4eXj5+gcFINiDClmAgsGEe3mGwAgxgdVExcRCZzGwFefC9cnrsaurRYP4+MNIADBIA3pQwzzA1sABe9Rraegb7n11rD1kv1PAthkEAO6lABWfEUwC0wmAAGs+qdzpcgrcni8smx3vtCiUyhUqgBfI5wdgAQjYGM8WLEDzapkqozOjKuyB5MBuMEhi3congkRADHiHT8LDSEtxzzeME+qDUTRarKV5gS9l6LmFg18AShsPhsERyLRLh0Zr8kRsYAA5LAiiAIBAdHRInURPKYPjCRqksJKJSgA

``` typescript
const foo = [1,2,3,4,5]
const bar = (_: number) => _

const x = foo.shift() // x is undefined even though foo has elements defined above
bar(x) // type error

const y = foo[99] // y is defined even though foo does not have that many elements
bar(y) // fine

if (foo.length > 0) {
    const z = foo.shift() // z is undefined even though we just checked foo.length > 0
    bar(z) // type error
}

if (!(foo.length)) { // or foo.length === 0 whatever floats your boat
    const z = foo[99] // z is defined even though we just checked it couldn't possibly be
    bar(z) // fine
}
```

### 💻  Use Case

I can understand that it always **could** be `undefined`, e.g. in this case:

``` typescript
const x: number[] = [1, 2, 3]

;(async () => {
  await longAsyncTask();
  while (x.length > 0) x.shift()
})()

;(async () => {
  await longAsyncTask();
  console.log(x.shift()) // could be undefined if the previous async block executes first
})()
```

However, this is a rarer case than `x[n]` which is much more commonly `undefined`. Given that `x[n]` is by default assumed `defined` unless the `--noUncheckedIndexedAccess` compiler flag is provided, `.shift()` should follow the same pattern.

Currently we use `.shift()` and a queue-based structure rather than random access, but TypeScript actually _penalizes_ this. The only ways around this are assertions `!` or adding an extra call for random access.

Workaround 1 (`!`):
``` typescript
const foo = [1,2,3,4,5]
const bar = (n: number) => n

bar(foo[99]) // somehow fine
bar(foo.shift()) // error?
bar(foo.shift()!) // workaround
```

Consider the following case as why this is a problematic anti-pattern:

``` typescript
const foo = myArray.map(myFunc) // I write this thinking the signature of foo is number[]
// however due to a bug in myFunc, signature is actually (number | undefined)[]

bar(foo.shift()!) // I type this as my workaround to behaviour described above
// now no type checking occurs above and I have a much more obscure bug in my code!
```

Workaround 2 is more type-safe (extra call for random access):

``` typescript
bar(foo[0])
foo.shift();
```

However, this is an anti-pattern at best.