for...in
The for...in loop walks over the enumerable string keys of an object, giving you each property name one at a time. It is the idiomatic way to inspect a plain object’s own properties, but it carries two famous traps: it climbs the prototype chain by default, and it is the wrong tool for arrays. Understanding both is the difference between code that quietly breaks and code you can trust.
Basic syntax
for...in binds a variable to each key (always a string) and runs the body once per key. You read the value with bracket notation.
const user = { name: "Ada", role: "engineer", active: true };
for (const key in user) {
console.log(`${key} = ${user[key]}`);
}
Output:
name = Ada
role = engineer
active = true
Use const for the loop variable — it is reassigned fresh on each iteration, so there is no need for let. The order of keys is well defined in modern engines: integer-like keys come first in ascending numeric order, then string keys in insertion order.
The inherited-property pitfall
for...in does not stop at the object’s own properties. It visits every enumerable property along the prototype chain. With plain object literals this rarely bites, but the moment you add to a prototype — or iterate an instance of a custom class with enumerable inherited props — the extra keys show up.
const base = { shared: "from prototype" };
const obj = Object.create(base);
obj.own = "from instance";
for (const key in obj) {
console.log(key);
}
Output:
own
shared
To restrict the loop to the object’s own properties, guard each key with Object.prototype.hasOwnProperty. Calling it via Object.prototype (or the modern Object.hasOwn) is safer than obj.hasOwnProperty(key), because the object might itself define a property named hasOwnProperty.
for (const key in obj) {
if (Object.hasOwn(obj, key)) {
console.log(key); // only "own"
}
}
Tip:
Object.hasOwn(obj, key)(ES2022) is the preferred replacement forObject.prototype.hasOwnProperty.call(obj, key). It is shorter, never shadowed, and reads naturally.
A cleaner alternative: Object.keys
In most code you do not actually want prototype keys, so reaching for for...in plus a guard is busywork. Object.keys returns an array of the object’s own enumerable string keys — already filtered for you — which you can iterate with for...of.
const config = { host: "localhost", port: 8080, tls: false };
for (const key of Object.keys(config)) {
console.log(`${key}: ${config[key]}`);
}
// Or grab key/value pairs together:
for (const [key, value] of Object.entries(config)) {
console.log(`${key}: ${value}`);
}
| Tool | Iterates | Includes inherited? | Returns |
|---|---|---|---|
for...in | keys | Yes (enumerable) | string keys, one per iteration |
Object.keys() | keys | No (own only) | array of keys |
Object.entries() | key/value | No (own only) | array of [key, value] pairs |
Object.getOwnPropertyNames() | keys | No | array incl. non-enumerable |
Why not to use it on arrays
Arrays are objects, so for...in technically “works” on them — and that is exactly the problem. It iterates the index keys as strings, in a spec order that historically was not guaranteed, and it will happily include any extra enumerable properties you (or a library) attached to the array.
const colors = ["red", "green", "blue"];
colors.tag = "primary"; // a stray property
for (const i in colors) {
console.log(i, typeof i);
}
Output:
0 string
1 string
2 string
tag string
Two things go wrong: the indices arrive as strings ("0", not 0), and the unrelated tag property is enumerated. For ordered, value-based iteration of an array, use for...of or array methods like forEach, map, and filter.
const colors = ["red", "green", "blue"];
for (const color of colors) {
console.log(color); // red, green, blue — values, in order
}
Warning: Never use
for...into loop over an array’s elements. Reach forfor...of(values) orcolors.entries()(index + value) instead.
Best practices
- Use
for...inonly to enumerate the keys of a plain object, not arrays, Maps, or Sets. - Guard with
Object.hasOwn(obj, key)whenever inherited properties are possible. - Prefer
Object.keys()/Object.entries()withfor...of— they are own-only by default and read more clearly. - For arrays, use
for...of,forEach, or.entries(); neverfor...in. - Remember that keys from
for...inare always strings, even numeric-looking ones. - Avoid mutating the object inside a
for...inloop; add or delete keys before or after iterating.