Command Palette

Search for a command to run...

Hectal
PHASE 2Beginner ~26 min· topic 7 of 7

Topic 2.7

for...of and for...in

In one line

for...of walks through the values of anything iterable (arrays, strings, Maps, Sets), while for...in walks through the property names (keys) of an object, including inherited ones. Use for...of for lists and Object.keys/entries with for...of for objects.

Think of it like this

A bookshelf and a filing cabinet. To read every book on the shelf, you take them one book at a time, in order: that's for...of, which gives you each value. A filing cabinet has labelled drawers; for...in reads you the labels on the drawers (the keys), not what's inside, and it even reads out labels on drawers inherited from the cabinet underneath.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Iterable
A value whose items can be taken one at a time in order, such as an array, string, Map or Set.
for...of
A loop that gives you each value of an iterable.
for...in
A loop that gives you each property name (key) of an object, as a string, including inherited ones.
Key / property name
The label of a value in an object, like name in { name: "Asha" }.
Enumerable
A property that shows up in for...in and Object.keys. Properties you add normally are enumerable; built-in methods are not.
Own vs inherited
An own property is stored on the object itself; an inherited one comes from its prototype (a parent object it borrows from).
Object.entries
A function that turns an object into an array of [key, value] pairs, ready for for...of.

Step by step

01Loop over values with for...of

No index and no length check: the loop hands you each value in order and stops after the last one.

of.jswhole filejavascript
const colours = ["red", "green", "blue"];
for (const colour of colours) {
  console.log(colour);
}

02It works on every iterable

Strings give characters, Sets give values, Maps give [key, value] pairs. Same loop, different collections.

iterables.jswhole filejavascript
for (const ch of "hey") console.log(ch);           // h, e, y
for (const n of new Set([1, 1, 2])) console.log(n);  // 1, 2
for (const [k, v] of new Map([["a", 1]])) console.log(k, v); // a 1

03Need the index? Use entries()

array.entries() gives [index, value] pairs, and destructuring unpacks them into two variables.

entries.jswhole filejavascript
const podium = ["Asha", "Ravi", "Meera"];
for (const [i, name] of podium.entries()) {
  console.log(i + 1, name);
}

04Loop over an object's keys with for...in

for...in gives keys as strings. Read the value with brackets: obj[key] (dot notation can't use a variable).

in.jswhole filejavascript
const user = { name: "Asha", age: 30 };
for (const key in user) {
  console.log(key, "=", user[key]);
}

05Pick the right loop

Use this decision path whenever you need to loop over something.

Pick the right loopdiagram
Rendering diagram…

Try it yourself

  1. 1

    Loop over a word

    In the first example, add for (const ch of "loop") console.log(ch);. Predict the four lines, then run.

  2. 2

    Swap in and of

    In the second example, change the first loop to for (const key of user). Predict what happens (see Break it) before you run.

  3. 3

    Add a key

    In the third example, add book.pages = 180; before the loop. Predict where pages appears on the page and in the logged keys.

Code & diagrams

for...of over different iterables ES2015+ New tab

The emoji counts as 2 in `.length` but as one character in `for...of`. The Set drops the duplicate `js`.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

0 apple
1 banana
2 cherry
length: 4 characters: 3
tag js
tag css
pens 12
books 3
for...in on objects (and why not on arrays) New tab

`for...in` gave string keys (`"0" + 1` is `"01"`) and the extra `label` property. `for...of` gave exactly the three values.

Console

(nothing was printed)

Expected console output

name -> Asha
age -> 30
city -> Pune
1 2 b a
0 string 01
1 string 11
2 string 21
label string label1
10
20
30
Objects: Object.entries with for...of, and inherited keys New tab

`Object.create(defaults)` makes an object that inherits from `defaults` (prototypes are Topic 7.3). `for...in` shows the inherited `theme`; `Object.keys` and `Object.hasOwn` don't.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

[ 'title', 'author', 'year' ]
for...in: fontSize
for...in: theme
own only: fontSize
[ 'fontSize' ]

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Use for...of on a plain object

In the second example, change for (const key in user) to for (const key of user).

terminal
$ Console
── what you'll see ──
Uncaught TypeError: user is not iterable

Break #2

Do maths with for...in indexes

Loop over an array with for (const i in nums) and compute the next index with i + 1.

terminal
$ Console
── what you'll see ──
01
11
21

Myth vs fact

Myth

for...in is the way to loop over an array.

Fact

It loops over property names as strings, includes non-index properties, and may include inherited ones. Use for...of or a counting loop for arrays.

Myth

for...of works on any object.

Fact

It only works on iterables. Plain objects aren't iterable; use Object.entries(obj) to get an iterable array of pairs.

Myth

Object keys come out in a random order.

Fact

The order is defined by the spec: integer-like keys ascending, then string keys in insertion order (then symbols, which loops skip). Don't rely on it for meaning, but it is predictable.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    for...of uses the iteration protocol: it calls obj[Symbol.iterator]() to get an iterator, then calls next() until it returns { done: true }. Any object can become iterable by defining that method; you'll do it in Topic 14.1. On early exit (break, return, throw) it calls the iterator's return() method.

  • ▸

    For plain arrays, V8 optimises for...of to roughly the speed of an indexed for loop. Differences only show up in micro-benchmarks; choose by clarity.

  • ▸

    Mutating a collection while iterating has defined but surprising behaviour: an array iterator reads length on every step, so pushing inside for...of can loop forever; a Set or Map visits entries added during iteration. for...in doesn't guarantee whether properties added during the loop are visited. Iterate over a copy if you need to change the original.

  • ▸

    Live DOM collections like element.children are iterable, and removing children while looping over them skips items, because the collection shrinks under you. Loop over a static copy ([...element.children] or Array.from(...)) instead (Phase 8).

Remember this

  1. 1

    **for...of (since ES2015) loops over the values of an iterable**. An iterable is any value that knows how to hand out its items one by one, in order: arrays, strings, Map, Set, the arguments object, DOM lists like NodeList, and generators. The syntax is for (const item of list) { ... }. No counter, no index, no off-by-one: you get each item directly. Use const unless you reassign the variable inside the body; each iteration gets a fresh binding.

  2. 2

    On a string, for...of gives you one character at a time, and it handles emoji and other characters outside the basic range correctly (they are one character to for...of, even though .length counts them as 2; Topic 4.2 explains why). On a Map it gives [key, value] pairs; on a Set it gives each value. If you need the index of an array item as well, loop over array.entries(), which yields [index, value] pairs: for (const [i, item] of list.entries()) (the [i, item] part is destructuring, Topic 5.8).

  3. 3

    Plain objects are not iterable. for (const x of { a: 1 }) throws TypeError: ... is not iterable. To loop over an object, turn it into an array first: Object.keys(obj) (the keys), Object.values(obj) (the values) or Object.entries(obj) ([key, value] pairs), then use for...of (covered in depth in Topic 6.4). These only include the object's own properties, which is usually what you want.

  4. 4

    **for...in loops over the keys (property names) of an object, as strings**: for (const key in obj) { console.log(key, obj[key]); }. It visits every enumerable property (properties that are meant to show up in loops; built-in ones like toString are not enumerable), and it also walks up the prototype chain, visiting inherited enumerable properties (prototypes are Topic 7.3). The order is: integer-like keys in ascending order first, then the other string keys in the order they were added.

  5. 5

    **Don't use for...in on arrays. It gives the indexes as strings** ("0", "1"), so key + 1 becomes "01"; it also visits any extra properties added to the array, and inherited ones if someone added to Array.prototype. For arrays use for...of, a counting for loop (Topic 2.5), or array methods (Phase 5).

  6. 6

    Which to use: values of a list, string, Map or Set → for...of. Keys and values of a plain object → for (const [key, value] of Object.entries(obj)). Need the index → for with a counter, or entries(). for...in is mostly for old code and quick inspection of an object; if you use it, guard with Object.hasOwn(obj, key) (ES2022) to skip inherited keys. A handy memory trick: **"of" gives values, "in" gives keys (names that are *in* the object).**

Explain it without notes

01

What is the difference between for...of and for...in?

02

Why shouldn't you use for...in to loop over an array?

03

How do you loop over the keys and values of a plain object?

04

What does "iterable" mean? Name four built-in iterables.

Practice

01

Given const prices = { tea: 20, coffee: 35, juice: 50 };, print each item and price as tea: 20 and then the total.

02

Count the vowels in the string "Hectal teaches JavaScript" with for...of.

03

Print each item of ["a", "b", "c"] with its position starting from 1, like 1. a.

Trade-offs

  • ↔

    for...of is the clearest loop for values and supports break/continue; array methods like map and filter (Phase 5) are clearer still when you're building a new array.

  • ↔

    for...in is short for inspecting an object's keys but drags in inherited properties; Object.entries with for...of is a little longer and does exactly what you mean.

Done when you can

  • I can loop over arrays, strings, Maps and Sets with for...of.

  • I can get the index too, with entries().

  • I can loop over a plain object with Object.entries/keys/values.

  • I know for...in gives string keys and includes inherited ones.

  • I can explain why for...in is wrong for arrays.