174

In the MDN docs: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of

The for...of construct is described to be able to iterate over "iterable" objects. But is there a good way of deciding whether an object is iterable?

I've tried to find common properties for arrays, iterators and generators, but have been unable to do so.

Aside from doing a for ... of in a try block and checking for type errors, is there a clean way of doing this?

BinaryButterfly
  • 18,137
  • 13
  • 50
  • 91
simonzack
  • 19,729
  • 13
  • 73
  • 118
  • Surely, as the author, you know if your object is iterable? – andrewb Sep 18 '13 at 23:58
  • 16
    The object is passed as an argument, I'm not certain. – simonzack Sep 18 '13 at 23:59
  • Right but I mean you will know what the type of object is returned? – andrewb Sep 19 '13 at 00:02
  • Might be a probable duplicate of http://stackoverflow.com/questions/7069316/how-to-test-if-an-object-is-a-collection-which-can-accept-each-in-jquery – Raghu Sep 19 '13 at 00:03
  • 1
    Why not test the argument's typeof? – James Bruckner Sep 19 '13 at 00:04
  • 2
    @andrew-buchan, James Bruckner: Checking for types may work, but if you read the MDN docs, you will notice that it says "array-like". I don't know what this means, exactly, hence the question. – simonzack Sep 19 '13 at 00:04
  • Do `arguments` and `NodeList` count as iterables? – Waleed Khan Sep 19 '13 at 00:05
  • `arguments` is not, but `NodeList` is. – simonzack Sep 19 '13 at 00:07
  • That was the first thing I did before asking this question, it doesn't work for arrays. – simonzack Sep 19 '13 at 00:13
  • 1
    http://wiki.ecmascript.org/doku.php?id=harmony:iterators states "*An object is iterable if it has an `iterator()` method.*". Yet, since this is a draft only a check might be implemenatation-dependent. What environment do you use? – Bergi Sep 19 '13 at 16:34
  • Array-like === Iterable Spread Operator === Iterable Rest Params === Iterable Object !== Iterable You can convert between types if needed. Here is a handy URL to learn more about the Iterable and Iterator protocols in ES6 (I'm still learning too, which is what brought me here). MDN had this handy info: Built-in iterables String, Array, TypedArray, Map and Set are all built-in iterables, because the prototype objects of them all have a Symbol.iterator method. – Benjamin Dean Dec 31 '16 at 07:19

10 Answers10

240

The proper way to check for iterability is as follows:

function isIterable(obj) {
  // checks for null and undefined
  if (obj == null) {
    return false;
  }
  return typeof obj[Symbol.iterator] === 'function';
}

Why this works (iterable protocol in depth): https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Iteration_protocols

Since we are talking about for..of, I assume, we are in ES6 mindset.

Also, don't be surprised that this function returns true if obj is a string, as strings iterate over their characters.

mrm
  • 5,001
  • 2
  • 32
  • 30
Tomas Kulich
  • 14,388
  • 4
  • 30
  • 35
  • 27
    or, `Symbol.iterator in Object(obj)`. –  Sep 12 '15 at 13:40
  • 18
    There is (at least) one exception to using 'in' operator: string. String is iterable (in terms of for..of) but you cannot use 'in' on it. If not for this, I'd prefer using 'in' it looks definitely nicer. – Tomas Kulich Dec 18 '15 at 15:12
  • 2
    shouldn't it be `return typeof obj[Symbol.iterator] === 'function'`? "In order to be iterable, an object must implement the @@iterator method" – it specifies method – callum Mar 18 '16 at 15:43
  • There is no proper semantics for obj[Symbol.iterator] being anything else than (undefined or) function. If anyone put for example String there, it's a bad thing and IMO it's good if the code fails as soon as possible. – Tomas Kulich Mar 20 '16 at 14:07
  • 1
    Would `typeof Object(obj)[Symbol.iterator] === 'function'` work in all cases? – Craig Gidney May 05 '16 at 05:35
  • Should this check up the prototype chain ? In order to be iterable, an object must implement the @@iterator method, meaning that the object (or one of the objects up its prototype chain) must have a property with a Symbol.iterator key: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators – Yasin Yaqoobi Sep 02 '16 at 19:50
  • `return obj && typeof obj[Symbol.iterator] === 'function'` In my case, `obj` won't be a string and if it is I don't want to iterate over it anyway. – ThaJay Sep 07 '18 at 12:05
  • @TatzyXY could you please provide more info, why it doesn't work for you? – Tomas Kulich Nov 13 '18 at 15:35
  • @TomasKulich In IE 11 `typeof obj[Symbol.iterator]` is not a function. There is just the object saved and not a function. Thats the reason why this code does not work in IE 11. – TatzyXY Nov 13 '18 at 15:59
  • @TatzyXY Symbols are not (fully) implmented in IE. Are you including babel-polyfill (or some other polyfill that'd do the job) ? Try including for example this: https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/7.0.0/polyfill.js and it should work. – Tomas Kulich Nov 16 '18 at 23:09
  • Always gotta love Javascript and its magic strings.. – Emperor Eto Apr 06 '21 at 11:56
  • In more recent versions of Javascript, you can just do this: `typeof obj?.[Symbol.iterator] === 'function'`, or just `!!obj?.[Symbol.iterator]` if you don't care about the function test. – Scotty Jamison May 29 '21 at 15:04
65
if (Symbol.iterator in Object(value)) {

}

Object will box anything which isn't an object into one. This prevents the in operator from throwing exceptions and removes the need to check for edge cases. null and undefined become empty objects, and the condition evaluates to true for a string, array, Map, Set, and so on.

Alternatively, in an environment that supports optional chaining:

if (value?.[Symbol.iterator]) {

}

The ?. syntax causes null and undefined to be ignored, and property access is safe on all other value types. This might not read like English, but checking the truthiness of a property to assert its existence is fairly idiomatic.

I would personally recommend inlining this code instead of defining a function, as it is relatively short and less ambiguous than a call to some isIterable(x) function.


Keep in mind that the word iterable specifically refers to the iterable protocol, though there exist different ways to loop over objects. The following list is not exhaustive:

Domino
  • 6,314
  • 1
  • 32
  • 58
32

Why so verbose?

const isIterable = object =>
  object != null && typeof object[Symbol.iterator] === 'function'
adius
  • 13,685
  • 7
  • 45
  • 46
  • 77
    `Readability > Cleverness` always returns `true`. – jfmercer Nov 07 '16 at 19:34
  • 36
    Haha, normally I'm the one to complain about unreadable code, but actually I think this is pretty readable. Like an english sentence: If the object is not null and the symbolic iterator property is a function, then it is iterable. If that's not dead simple, I don't know what is ... – adius Jan 27 '17 at 19:21
  • 4
    imo, the point of "readability" is to understand what's happening without _actual_ reading – Parzh from Ukraine Apr 22 '17 at 21:23
  • 2
    @Alexander Mills Your improvement made the code worse. 1. Like @jfmercer said `Readability > Cleverness`, so shortening the variable `object` to `o` helps nobody. 2. An empty string `''` is iterable and so it must return `true`. – adius Nov 14 '17 at 11:26
  • 1
    Got it, I figured there was a reason why it was using `!= null` – Alexander Mills Nov 14 '17 at 15:33
  • 4
    Maybe, the only thing you need to read is the name: `isIterable`. – Matthias Apr 03 '20 at 20:26
  • Using `object` as the argument name here isn't quite right because the check works for more than just objects. Better to use something generic, like `x` or `value`.Other than that, in 2021 this actually feels the most readable to me. Funny how our perceptions about readability change. – apostl3pol Nov 11 '21 at 02:20
17

2022 Answer

If you are asking "Is foo iterable" then you probably come from a language (PHP, Python) where that question has a single answer. In modern Javascript, there are different types of iterable. Therefore, you have to check the ability to iterate depending on what you want to do with the variable.

TL;DR

  • Test the ability to iterate using forEach() with !!foo.forEach, returns true on an Array.
  • Test the ability to iterate using for..of with !!foo[Symbol.iterator], returns true on an Array or String.
  • Test the ability to iterate using for..in with !!Object.keys(Object(foo)).length, returns true on an Array, String, or Object.

Long answer

Let's define some variables:

const someNumber = 42;
42

const someArray = [1,2,3];
(3) [1, 2, 3]

const someString = "Hello";
"Hello, world!"

const someObject = {a:"A", b:"B"};
{a: "A", b: "B"}

Testing iterability with forEach()

Which types can be iterated with forEach(), tested with !!foo.forEach:

someNumber.forEach(x=>console.log(x));
VM1526:1 Uncaught TypeError: someNumber.forEach is not a function at <anonymous>:1:12

someArray.forEach(x=>console.log(x));
VM916:1 1
VM916:1 2
VM916:1 3
undefined

someString.forEach(x=>console.log(x));
VM957:1 Uncaught TypeError: someString.forEach is not a function at <anonymous>:1:12

someObject.forEach(x=>console.log(x));
VM994:1 Uncaught TypeError: someObject.forEach is not a function at <anonymous>:1:12

Only the Array seems to be iterable with forEach().

Testing iterability with for..of

Which types can be iterated with for..of, tested with !!foo[Symbol.iterator]:

for (x of someNumber) { console.log(x); }
VM21027:1 Uncaught TypeError: someNumber is not iterable at <anonymous>:1:11
    
for (x of someArray) { console.log(x); }
VM21047:1 1
VM21047:1 2
VM21047:1 3
undefined

for (x of someString) { console.log(x); }
VM21065:1 H
VM21065:1 e
VM21065:1 l
VM21065:1 l
VM21065:1 o
undefined

for (x of someObject) { console.log(x); }
VM21085:1 Uncaught TypeError: someObject is not iterable at <anonymous>:1:11

​The Array and String seem to be iterable with for..of, but the Object is not. And both the Number and the Object threw an error.

Testing iterability with for..in

Which types can be iterated with for..in, tested with !!Object.keys(Object(foo)).length:

for (x in someNumber) { console.log(x); }
undefined

for (x in someArray) { console.log(x); }
VM20918:1 0
VM20918:1 1
VM20918:1 2
undefined

for (x in someString) { console.log(x); }
VM20945:1 0
VM20945:1 1
VM20945:1 2
VM20945:1 3
VM20945:1 4
undefined

for (x in someObject) { console.log(x); }
VM20972:1 a
VM20972:1 b
undefined

​The Array, String, and Object all seem to be iterable with for..in. And though it did not iterate, the Number did not throw an error.

In modern, ES6 Javascript I see forEach used far more often than for..in or for..of. But Javascript developers must be aware of the differences between the three approaches, and the different behaviour of each approach.

dotancohen
  • 30,064
  • 36
  • 138
  • 197
11

As a sidenote, BEWARE about the definition of iterable. If you're coming from other languages you would expect that something you can iterate over with, say, a for loop is iterable. I'm afraid that's not the case here where iterable means something that implements the iteration protocol.

To make things clearer all examples above return false on this object {a: 1, b: 2} because that object does not implement the iteration protocol. So you won't be able to iterate over it with a for...of BUT you still can with a for...in.

So if you want to avoid painful mistakes make your code more specific by renaming your method as shown below:

/**
 * @param variable
 * @returns {boolean}
 */
const hasIterationProtocol = variable =>
    variable !== null && Symbol.iterator in Object(variable);
Francesco Casula
  • 26,184
  • 15
  • 132
  • 131
  • You are making no sense. Why do you think it would break with `undefined`? – adius Nov 14 '17 at 11:22
  • @adius I think I wrongly assumed you were doing `object !== null` in your answer but you're doing `object != null` so it's not breaking with `undefined` in that specific case. I've updated my answer accordingly. – Francesco Casula Nov 14 '17 at 11:35
  • Ok, I see. Btw: Your code is incorrect. `hasIterationProtocol('')` must return `true`! How about you remove your code and just leave the iterable explanation section, which is the only thing that adds real value / something new in your answer. – adius Nov 14 '17 at 11:48
  • 1
    It does return `true`, by removing part of the old answer I merged both functions and forgot about the strict comparison. Now I put back the original answer which was working fine. – Francesco Casula Nov 14 '17 at 11:52
  • 1
    I never would have thought of using `Object(...)`, nice catch. But in that case the null check is not needed. – Domino Nov 01 '18 at 17:57
6

For async iterators you should check for the 'Symbol.asyncIterator' instead of 'Symbol.iterator':

async function* doSomething(i) {
    yield 1;
    yield 2;
}

let obj = doSomething();

console.log(typeof obj[Symbol.iterator] === 'function');      // false
console.log(typeof obj[Symbol.asyncIterator] === 'function'); // true
Kamarey
  • 10,832
  • 7
  • 57
  • 70
6

If the object has the property Symbol.iterator then it is iterable. Then we can simply check if obj is iterable like this

TypeScript

function isIterable(x: unknown): boolean {
  return !!x?.[Symbol.iterator];
}

Or as an arrow function

const isIterable = (x: unknown): boolean => !!x?.[Symbol.iterator];

JavaScript

const isIterable = x => !!x?.[Symbol.iterator];

Examples

isIterable(["hello", "world"]); // true
isIterable({}); // false
isIterable(null); // false
isIterable(undefined); // false
isIterable(1); // false
isIterable(true); // false
isIterable(Symbol("foo")); // false
isIterable(new Set()); // true
isIterable(new Map()); // true
Zuhair Taha
  • 2,808
  • 2
  • 35
  • 33
4

Nowadays, as already stated, to test if obj is iterable just do

obj != null && typeof obj[Symbol.iterator] === 'function' 

Historical answer (no more valid)

The for..of construct is part of the ECMASCript 6th edition Language Specification Draft. So it could change before the final version.

In this draft, iterable objects must have the function iterator as a property.

You can the check if an object is iterable like this:

function isIterable(obj){
   if(obj === undefined || obj === null){
      return false;
   }
   return obj.iterator !== undefined;
}
jfmercer
  • 3,641
  • 3
  • 29
  • 35
Ortomala Lokni
  • 56,620
  • 24
  • 188
  • 240
  • 8
    The iterator property was a temporary firefox thing. It's not ES6 compliant. see: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Iteration_protocols#Iterator_property_and_iterator_symbol – Amir Arad Dec 30 '15 at 07:51
2

If you wanted to check in fact if a variable is an object ({key: value}) or an array ([value, value]), you could do that:

const isArray = function (a) {
    return Array.isArray(a);
};

const isObject = function (o) {
    return o === Object(o) && !isArray(o) && typeof o !== 'function';
};

function isIterable(variable) {
    return isArray(variable) || isObject(variable);
}
Seglinglin
  • 447
  • 1
  • 4
  • 17
0

I was looking for a check for for ... in and decided on the following.

isIterable (value) {
  // add further checks here based on need.
  return Object.keys(Object(value)).length > 0
}

This will return true for anything that is iterable and has at least one value. Therefore empty strings, empty arrays, empty objects etc. will return false. But {a: 'x', b:'y'} will return true.

Timar Ivo Batis
  • 1,861
  • 17
  • 21