Under strict alone, names[2] on a string[] is a string, even when the array has two elements, and a key looked up in a Record<string, number> is a number whether or not it exists. noUncheckedIndexedAccess adds undefined to those lookups, so each one needs a check, like any other value that may be missing.
It arrived with TypeScript 4.1 and is not part of strict. The strict level of the jsconfig generator turns it on, and so does the config that tsc --init writes in TypeScript 5.9.
At a glance
| Write | "noUncheckedIndexedAccess": true once strict passes; tsc --init in TypeScript 5.9 writes it |
|---|---|
| On the command line | --noUncheckedIndexedAccess |
| Type | boolean |
| Default in TypeScript 5.9 | false |
| TypeScript's description | Add 'undefined' to a type when accessed using an index. |
| Generator, relaxed | not written |
| Generator, recommended | not written |
| Generator, strict | true |
The description is quoted from TypeScript's own option list (Apache-2.0); see the notices. The generator rows are what the jsconfig generator writes at each level.
Examples
const names = ["Ada", "Grace"];
const third = names[2];
console.log(third.toUpperCase());error TS18048: 'third' is possibly 'undefined'.
/** @type {Record<string, number>} */
const stock = { tea: 12 };
console.log(stock.cake.toFixed(0));error TS18048: 'stock.cake' is possibly 'undefined'.
for (const name of names) console.log(name.toUpperCase());jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": [],
"noUncheckedIndexedAccess": true
},
"include": [
"*.js"
]
} const names = ["Ada", "Grace"];
const third = names[2];
console.log(third.toUpperCase());
/** @type {Record<string, number>} */
const stock = { tea: 12 };
console.log(stock.cake.toFixed(0));
for (const name of names) console.log(name.toUpperCase());jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": [
"*.js"
]
} What changes and what does not
Lookups by index or by key gain undefined; iteration does not:
array[i], and destructuring such asconst [first] = arrayrecord[key]andrecord.keyfor keys that come from an index signaturefor (const item of array),array.forEachandarray.mapare unchanged, because they only visit elements that exist- tuples keep their exact types: the second element of a
[string, number]is anumber
Gotchas
- Loops written with an index,
for (let i = 0; i < a.length; i++) a[i]…, report everya[i]even thoughiis in range. Afor…ofloop reads better and needs no check.
Sources
- TSConfig Reference: noUncheckedIndexedAccess · TypeScript
- TypeScript 4.1 release notes · TypeScript handbook
- TypeScript 5.9 release notes · TypeScript handbook
Last reviewed 20 October 2026.