| name | avoid-numeric-index |
| description | Use when defining array-like types. Use when tempted to use number as index type. Use when understanding array keys. |
Avoid Numeric Index Signatures
Overview
JavaScript object keys are always strings, even for arrays.
TypeScript's numeric index signatures are a helpful fiction for catching mistakes, but they don't reflect JavaScript's runtime behavior. Use Array, tuple, or ArrayLike types instead.
When to Use This Skill
- Defining array-like types
- Understanding JavaScript's array behavior
- Choosing between arrays and objects
- Working with Object.keys on arrays
The Iron Rule
NEVER use number as an index signature type.
Use Array<T>, tuple, or ArrayLike<T> instead.
Remember:
- JavaScript converts all object keys to strings
- Array indices are strings at runtime
Object.keys always returns strings
- TypeScript's number index is compile-time only
Detection: The JavaScript Reality
const obj = { 1: 'one', 2: 'two' };
console.log(Object.keys(obj));
const arr = ['a', 'b', 'c'];
console.log(Object.keys(arr));
console.log(arr['1']);
TypeScript's Helpful Fiction
TypeScript pretends arrays have numeric indices:
interface Array<T> {
[n: number]: T;
}
const xs = [1, 2, 3];
const x0 = xs[0];
const x1 = xs['1'];
const inputEl = document.querySelector('input')!;
const bad = xs[inputEl.value];
This is useful for catching mistakes, even though it's not technically accurate.
Why Avoid Numeric Index Signatures
Reality Leaks Through
const xs = [1, 2, 3];
const keys = Object.keys(xs);
for (const key of Object.keys(xs)) {
console.log(typeof key);
}
Creates False Mental Model
type NumericDict = { [key: number]: string };
const dict: NumericDict = { 0: 'zero', 1: 'one' };
console.log(Object.keys(dict));
Better Alternatives
Use Array for Sequences
type Bad = { [index: number]: string };
type Good = string[];
Use Tuple for Fixed Length
type Point = [number, number];
type RGB = [number, number, number];
const origin: Point = [0, 0];
Use ArrayLike for Array-like Structures
function sum(items: ArrayLike<number>): number {
let total = 0;
for (let i = 0; i < items.length; i++) {
total += items[i];
}
return total;
}
sum([1, 2, 3]);
sum({ 0: 1, 1: 2, 2: 3, length: 3 });
Use Iterable for Iteration Only
function sumIterable(items: Iterable<number>): number {
let total = 0;
for (const item of items) {
total += item;
}
return total;
}
sumIterable([1, 2, 3]);
sumIterable(new Set([1, 2, 3]));
function* nums() { yield 1; yield 2; yield 3; }
sumIterable(nums());
The String/Number Index Relationship
const arr = [1, 2, 3];
const first: number = arr[0];
const keys = Object.keys(arr);
const second = arr['1'];
Safe Array Access
const arr = [1, 2, 3];
const x = arr[10];
const y = arr[10];
function checkedAccess<T>(xs: ArrayLike<T>, i: number): T {
if (i >= 0 && i < xs.length) {
return xs[i];
}
throw new Error(`Index ${i} out of bounds`);
}
Pressure Resistance Protocol
1. "I Want a Sparse Array Type"
Pressure: "I need { [n: number]: T } for a sparse array"
Response: Use Map<number, T> for sparse data, or regular arrays with optional access.
Action: new Map<number, string>() for sparse numeric keys.
2. "I Need Numeric Keys for My Object"
Pressure: "My object uses IDs as keys: { 1: user1, 2: user2 }"
Response: Use Map<number, User> or Record<string, User> and convert.
Action: Embrace that keys are strings, or use Map.
Red Flags - STOP and Reconsider
{ [key: number]: T } in your own types
- Assuming
Object.keys(array) returns numbers
- Using numeric strings as array indices
- Confusion about why
typeof key is 'string'
Common Rationalizations (All Invalid)
| Excuse | Reality |
|---|
| "Arrays have numeric indices" | At runtime, they're strings |
| "TypeScript uses number in Array" | It's a convenient fiction for type safety |
| "I need sparse numeric keys" | Use Map<number, T> instead |
Quick Reference
type Bad = { [n: number]: string };
type Good = string[];
type Point = [number, number];
function process(items: ArrayLike<string>) { ... }
function process(items: Iterable<string>) { ... }
const sparse = new Map<number, string>();
The Bottom Line
Numeric index signatures are a TypeScript convenience, not JavaScript reality.
JavaScript object keys are always strings. TypeScript's numeric indices help catch mistakes but create a false mental model. Use Array, tuple, ArrayLike, or Map instead of defining your own numeric index signatures.
Reference
Based on "Effective TypeScript" by Dan Vanderkam, Item 17: Avoid Numeric Index Signatures.