Checking whether something is iterable

Javascript

Javascript Problem Overview


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?

Javascript Solutions


Solution 1 - Javascript

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.

Solution 2 - Javascript

The simplest solution is actually this:

function isIterable (value) {
  return Symbol.iterator in Object(value);
}

Object will wrap anything which isn't an object in one, allowing the in operator to work even if the original value is not an Object. null and undefined are turned into empty objects so there's no need for edge case detection, and strings get wrapped into String objects which are iterable.

Solution 3 - Javascript

Why so verbose?

const isIterable = object =>
  object != null && typeof object[Symbol.iterator] === 'function'

Solution 4 - Javascript

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);

Solution 5 - Javascript

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.

Solution 6 - Javascript

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

Solution 7 - Javascript

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;
}

Solution 8 - Javascript

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

Solution 9 - Javascript

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);
}

Solution 10 - Javascript

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionsimonzackView Question on Stackoverflow
Solution 1 - JavascriptTomas KulichView Answer on Stackoverflow
Solution 2 - JavascriptDominoView Answer on Stackoverflow
Solution 3 - JavascriptadiusView Answer on Stackoverflow
Solution 4 - JavascriptFrancesco CasulaView Answer on Stackoverflow
Solution 5 - JavascriptdotancohenView Answer on Stackoverflow
Solution 6 - JavascriptKamareyView Answer on Stackoverflow
Solution 7 - JavascriptOrtomala LokniView Answer on Stackoverflow
Solution 8 - JavascriptZuhair TahaView Answer on Stackoverflow
Solution 9 - JavascriptSeglinglinView Answer on Stackoverflow
Solution 10 - JavascriptTimar Ivo BatisView Answer on Stackoverflow