Use when iterating over object keys and values. Use when for...in loops produce type errors. Use when Object.entries returns any types. Use when dealing with prototype pollution concerns. Use when considering Map vs object.
Iterating over objects in TypeScript is surprisingly tricky. The for...in loop infers keys as string rather than the object's keys, leading to indexing errors. This happens because objects can have additional properties beyond their declared type (structural typing), and for...in includes inherited properties.
Understanding safe iteration patterns helps you avoid any types and type assertions while correctly handling object traversal.
for...in loops produce "Element implicitly has 'any' type" errorsObject.entries returns any value typesUse Object.entries for safe iteration over any object. Use for...in with keyof assertions only when you know the exact shape. Consider Map for guaranteed type safety.
Watch for these errors:
// ERROR: Element implicitly has 'any' type
for (const k in obj) {
const v = obj[k]; // Error!
}
// ERROR: Type 'string' cannot be used to index type 'ABC'
function foo(abc: ABC) {
for (const k in abc) {
const v = abc[k]; // Error!
}
}
interface ABC {
a: string;
b: string;
c: number;
}
function foo(abc: ABC) {
for (const k in abc) {
// k is string, not 'a' | 'b' | 'c'
const v = abc[k];
// ^? any - TypeScript gives up
}
}
// Why? Structural typing allows extra properties:
const x = { a: 'a', b: 'b', c: 2, d: new Date() };
foo(x); // Valid! x has at least ABC's properties
// k could be 'd', which isn't in ABC
function foo(abc: ABC) {
for (const [k, v] of Object.entries(abc)) {
// k: string (honest about what it is)
// v: any (honest about unknown values)
console.log(k, v);
}
}
Pros: Always safe, no type assertions needed
Cons: Values are any, keys are string
When you know exactly what keys exist:
const obj = { one: 'uno', two: 'dos', three: 'tres' };
// Explicitly list keys
const keys = ['one', 'two', 'three'] as const;
for (const k of keys) {
// k: 'one' | 'two' | 'three'
const v = obj[k]; // string - precise!
}
When you're sure about the object's shape:
const obj = { one: 'uno', two: 'dos', three: 'tres' };
for (const kStr in obj) {
const k = kStr as keyof typeof obj;
// k: 'one' | 'two' | 'three'
const v = obj[k]; // OK
}
Warning: Only safe when you control object creation and know it has no extra properties.
const m = new Map([
['one', 'uno'],
['two', 'dos'],
['three', 'tres'],
]);
for (const [k, v] of m) {
// k: string (known type)
// v: string (known type)
console.log(k, v);
}
Pros: Guaranteed types, no prototype pollution Cons: Less convenient for JSON data, different API
interface Config {
host: string;
port: number;
ssl: boolean;
}
// Safe iteration with Object.entries
function printConfig(config: Config) {
for (const [key, value] of Object.entries(config)) {
console.log(`${key}: ${value}`);
}
}
// Type-safe with known keys
function validateConfig(config: Config): string[] {
const errors: string[] = [];
const requiredKeys = ['host', 'port', 'ssl'] as const;
for (const key of requiredKeys) {
if (!(key in config)) {
errors.push(`Missing: ${key}`);
}
}
return errors;
}
When object iteration causes type issues:
| Anti-Pattern | Problem | Solution |
|---|---|---|
obj[k] in for...in | k is string, unsafe index | Use Object.entries |
as keyof T on parameters | Object might have extra keys | Object.entries or Map |
| Ignoring inherited properties | Prototype pollution risk | Object.entries excludes these |
as any"Reality: Object.entries gives you the same any but honestly. No need to bypass type safety.
Reality: TypeScript's structural typing means it could. Be explicit about your assumptions.
Reality: It's more verbose but also more type-safe. Worth it for critical code.
| Approach | Key Type | Value Type | Safety |
|---|---|---|---|
for...in | string | any (error) | Unsafe |
Object.entries | string | any | Safe |
| Known keys array | Specific | Specific | Safe |
keyof assertion | Specific | Specific | Risky |
| Map | Specific | Specific | Safe |
Object iteration is tricky due to structural typing and prototype pollution. Use Object.entries for safety, explicit key arrays for precision, or Map for guaranteed type safety.