Why does indexing in an array break type safety in TypeScript?

Viewed 1503

The whole point of adding static types to JavaScript is to provide some guarantees about type safety. I noticed that array indexing seems to break type safety without using any dirty tricks like as any or the not null assertion operator.

let a: Array<number> = [1,2,3,4];
let b: number = a[4]; //undefined

This code does not cause any TypeScript errors, even though it is plain to see that it will violate type safety. It seems to me that the type of an Array<T> acted upon by the index operator [] should be type T | undefined, however the TypeScript compiler treats it as if it was type T.

Upon further investigation, I discovered that this behavior applies to use of the index operator on objects as well. It would seem that the index operator is not type safe in any case.

class Example {
  property: string;
}

let c: Example = { property: "example string" }
let d: string = c["Not a property name"]; //undefined

Use of the index operator on an object with arbitrary key returns type any, which can be assigned to any type without causing type errors. However, this can be resolved by using the --noImplicitAny compiler option.

My question is why does something as basic as indexing on an array break type safety? Is this a design constraint, an oversight, or a deliberate part of TypeScript?

3 Answers

To answer the why part of your question, it is because the developers of TypeScript thought that it would be too uncomfortable if you always had to check for undefined results when indexing an Array.

For example, the following code would not type-check:

var arr: number[]
var s = 0
for (var i in arr) {
  s += arr[i]
}

Furthermore, because JavaScript Arrays can be sparse, even if the index is known to not be out of bounds (as in, >= 0 and < arr.length), you can still get undefined, so there seems to be no way to fully infer actual unsafe indexing.

For more on this, see the issues TypeScript#13778 and TypeScript#11122.

Since Typescript version 4.1 we have the setting noUncheckedIndexedAccess. Enabling this basically works like what you describe - the inferred type of index access always includes undefined. In your example, the type of b becomes T | undefined.

Related