Skip to main content
Beta: Front-End Checklist is currently in beta. Some issues are still being fixed. Thanks for your patience.

Enable noUncheckedIndexedAccess to catch out-of-bounds array bugs

Enable noUncheckedIndexedAccess in tsconfig.json to make array and object index access return T | undefined, forcing explicit null checks that prevent out-of-bounds runtime errors.

Utilities
Quick take
Typical fix time 15 min
  • Without the flag, array[0] is typed as T even if the array is empty
  • noUncheckedIndexedAccess changes array[n] to return T | undefined
  • Forces an explicit check before using any array element accessed by index
  • Optional chaining and nullish coalescing handle most false positives cleanly
Why it matters: TypeScript's default behaviour types `array[0]` as `T`, pretending the access is always safe. In reality, the array might be empty or the index might be out of range, leading to silent `undefined` values that cause downstream crashes. Enabling `noUncheckedIndexedAccess` surfaces these potential failures as type errors at compile time, requiring developers to prove the access is safe before the code runs.

Rule Details

TypeScript's type system makes an optimistic assumption about array index access: array[0] is typed as T, not T | undefined. The noUncheckedIndexedAccess flag (opens in new tab) and the TypeScript 4.1 checked indexed access notes (opens in new tab) exist because that default is unsound.

Code Example

// tsconfig.json
{
  "compilerOptions": {
    "strict": true,
    // strict mode does NOT include noUncheckedIndexedAccess — add it explicitly
    "noUncheckedIndexedAccess": true
  }
}

Why It Matters

TypeScript's default behaviour types array[0] as T, pretending the access is always safe. In reality, the array might be empty or the index might be out of range, leading to silent undefined values that cause downstream crashes. Enabling noUncheckedIndexedAccess surfaces these potential failures as type errors at compile time, requiring developers to prove the access is safe before the code runs.

A Real Bug Caught by the Flag

// ❌ Without noUncheckedIndexedAccess — TypeScript sees string, not string | undefined
function getFirstTag(tags: string[]): string {
  return tags[0].toUpperCase() // TypeScript: fine. Runtime: TypeError if tags is []
}
 
getFirstTag([]) // Uncaught TypeError: Cannot read properties of undefined
 
// ✅ With noUncheckedIndexedAccess — TypeScript error forces a check
function getFirstTag(tags: string[]): string {
  const first = tags[0] // TypeScript: string | undefined
  if (first === undefined) {
    return 'untagged'
  }
  return first.toUpperCase() // safe: first is string here
}
 
// ✅ Concise version with nullish coalescing
function getFirstTag(tags: string[]): string {
  return (tags[0] ?? 'untagged').toUpperCase()
}
 
// ✅ Using Array.at() — also returns T | undefined
function getFirstTag(tags: string[]): string {
  return (tags.at(0) ?? 'untagged').toUpperCase()
}

The array.map + access by index Pattern

A subtle but common bug — mapping over one array and indexing into a parallel array:

const scores = [95, 87, 72]
const grades = ['A', 'B', 'C']
 
// ❌ Without the flag — TypeScript sees string, crashes if arrays differ in length
const report = scores.map((score, i) => `${grades[i]}: ${score}`)
//                                         ^^^^^^^^ string — but what if i >= grades.length?
 
// ✅ With noUncheckedIndexedAccess — grade is string | undefined, forces a check
const report = scores.map((score, i) => {
  const grade = grades[i] ?? 'N/A'
  return `${grade}: ${score}`
})
 
// ✅ Better design: use a single array of objects instead of parallel arrays
const entries = [
  { grade: 'A', score: 95 },
  { grade: 'B', score: 87 },
  { grade: 'C', score: 72 },
]
const report = entries.map(({ grade, score }) => `${grade}: ${score}`)

Index Signatures

The flag also affects index signature access on objects:

type ScoreMap = Record<string, number>
 
const scores: ScoreMap = { alice: 95, bob: 87 }
 
// ❌ Without the flag — TypeScript sees number (could be undefined)
const charlieScore = scores['charlie'] // number — but actually undefined at runtime
 
// ✅ With the flag — TypeScript sees number | undefined, forces a check
const charlieScore = scores['charlie'] // number | undefined
const display = charlieScore !== undefined
  ? `Score: ${charlieScore}`
  : 'No score recorded'

Handling Array Iteration

Iterating with for...of or array methods is always safe — the flag only affects index syntax:

const items = ['apple', 'banana', 'cherry']
 
// ✅ for...of — item is string, no undefined
for (const item of items) {
  console.log(item.toUpperCase())
}
 
// ✅ forEach — item is string, no undefined
items.forEach(item => console.log(item.toUpperCase()))
 
// ✅ map/filter/reduce — element type is string, no undefined
const upper = items.map(item => item.toUpperCase())
 
// ⚠️ Index access — with the flag, items[i] is string | undefined
for (let i = 0; i < items.length; i++) {
  const item = items[i] // string | undefined even though i is in bounds
  console.log(item?.toUpperCase()) // optional chaining handles it
}
Array destructuring is not affected

Destructuring (const [first, second] = arr) does not get the | undefined treatment under noUncheckedIndexedAccess. first is still typed as T. For destructured access that might be out of bounds, add | undefined manually or use arr.at(0) instead.

Suppressing False Positives

For hot loops where you have already bounds-checked with i < arr.length, a non-null assertion is acceptable:

for (let i = 0; i < matrix.length; i++) {
  // We know matrix[i] exists because i < matrix.length
  const row = matrix[i]!  // '!' non-null assertion — justified by the loop guard
  for (let j = 0; j < row.length; j++) {
    const cell = row[j]!  // similarly justified
    processCell(cell)
  }
}

Use ! sparingly and only where the bounds check is immediately visible.

Verification

  1. Confirm "noUncheckedIndexedAccess": true is present in tsconfig.json under compilerOptions. The TSConfig reference (opens in new tab) is explicit that this flag is not enabled by "strict": true and must be added separately.
  2. Run pnpm exec tsc --noEmit after enabling the flag and fix every resulting type error — do not use ! non-null assertions to silence errors without verifying bounds.
  3. Search for [0] array access patterns in the codebase and confirm each one either has a null check, uses ?? or ?., or is inside a bounds-checked loop with a justified ! assertion.
  4. Check that no Record<string, T> or index signature types are accessed without handling the | undefined case introduced by the flag.

Use with AI

Copy these prompts to use with your AI assistant, or install the MCP server to use directly from Claude, Cursor, or Windsurf.

Check

Verify implementation

Check whether noUncheckedIndexedAccess is enabled in the project's tsconfig.json. If not, scan for array index accesses (arr[0], arr[i]) that could fail if the array is shorter than expected.

Fix

Auto-fix issues

Enable noUncheckedIndexedAccess in tsconfig.json, then update any array index accesses that TypeScript now flags to include an explicit undefined check or use optional chaining before the accessed value is used.

Explain

Learn more

Explain what noUncheckedIndexedAccess does, why the default TypeScript behaviour is unsound for array index access, and how optional chaining and Array.at() interact with the flag.

Review

Code review

Review all array index accesses in this file (arr[0], arr[i], object[key]). Flag any location where the accessed value is used without a preceding undefined check, even if noUncheckedIndexedAccess is not yet enabled in the project.

Sources

References used to support the guidance in this rule.

Rules that often go hand-in-hand with this one.

Enable TypeScript strict mode in tsconfig.json

Enable "strict": true in tsconfig.json to activate the full suite of TypeScript type-checking flags and catch the most common runtime bugs at compile time.

JavaScript
Avoid the any type — use unknown, generics, or type guards instead

Replace TypeScript's any type with unknown, proper generics, or narrowed type assertions to preserve type safety without sacrificing expressiveness.

JavaScript
Use modern array and object methods

Use ES2015+ array methods (map, filter, reduce, find, flatMap) and object methods (Object.entries, Object.fromEntries, structuredClone) for cleaner, more expressive code.

JavaScript
Parse JSON safely with error handling

Always wrap JSON.parse() in try/catch and validate the parsed structure before use, as invalid JSON or unexpected data shapes cause runtime errors.

JavaScript

Was this rule helpful?

Your feedback helps improve rule quality. This stays internal for now.

Loading feedback...
0 / 385