DEV Community

loading...

The moment I realized forEach() does not return anything.

kaynguyen profile image Kay N. Updated on ・3 min read

Javascript ES5's forEach() method and I had been getting along quite well until last week when I was trying to implement it inside this one simple function that takes in two arrays and decides whether they contain common items.

One of my two forEach() loops refused to do its job

So there I had two test arrays to begin with:

const animals = ["lion", "dog", "kangaroo"];
const pets = ["hamster", "parrot", "dog", "cat"];

haveCommonItems(animals, pets); // should return "true"
Enter fullscreen mode Exit fullscreen mode

Apparently the two inputs both contain "dog" so my function on being called should be able to return true in this case (and false in an hypothetically opposite one). For the sake of time complexity optimization, I decided to give it two independent loops (instead of nested ones), using Javascript ES5's Array.prototype.forEach() method:

const haveCommonItems = (array1, array2) => {
  array1.forEach(
    // converts array1 items into properties
    // returns an object that has those properties
  );

  array2.forEach(
    // checks if array2 items equal to properties on object created from first loop
    // returns a boolean
  );
}
Enter fullscreen mode Exit fullscreen mode

My first forEach() loop does its job by yielding the expected object, like so:

const haveCommonItems = (array1) => {
  let obj = {};
  array1.forEach(item => !obj[item] ? obj[item] = true : null);
  console.log(obj);
};

haveCommonItems(animals); // logs {lion: true, elephant: true, dog: true, kangaroo: true}
Enter fullscreen mode Exit fullscreen mode

Things started to confuse me as my second forEach() came along. On running the function I was expecting to get back true which never happened; instead I got a false:

const haveCommonItems = (array1, array2) => {
  let obj = {};
  array1.forEach(item => obj[item] ? null : obj[item] = true);

  array2.forEach(item => {
    if (obj[item]) {
      return true;
    }
  });

  return false;
};

haveCommonItems(animals, pets); // returns "false" (me: "c'mon gimme a break!")
Enter fullscreen mode Exit fullscreen mode

Apparently it seemed like either my array2.forEach() never actually looped through the array itself, or it did but it was just being selfish and not giving back anything.

I was right about the latter.

What actually was going on inside of that so-called loop?

I did a little research by jumping into the MDN web docs only to figure out this one major feature that I had paid almost no attention to before:

forEach() always returns the value undefined and is not chainable.

and

There is no way to stop or break a forEach() loop other than by throwing an exception.

It turned out I had been under the wrong impression that forEach() was there to completely take the job over from my old friend for() loop. In fact, forEach() takes in a callback function that does return a value, but only within the scope created by forEach(). In other words, the return statement inside that callback never brought the value out of its scope or exited the function; it only returned that value to forEach() which then continued to traverse the rest of the array before returning undefined to its boss haveCommonItems().

My function haveCommonItems() at this point (on getting that undefined value) had no idea what happened inside that forEach() (poor little guy!), so it continued on to reach its own return statement and gave me a false.

So yeah, while that AHA moment still lasted, I came up with somewhat a solution for it:

const haveCommonItems = (array1, array2) => {
  let obj = {};
  array1.forEach(item => obj[item] ? null : obj[item] = true);

  let found = false;
  array2.forEach(item => {
    if (obj[item]) {
      found = true;
    }
  });
  return found;
};

haveCommonItems(animals, pets); // returns "true" (me: "you better do!")
Enter fullscreen mode Exit fullscreen mode

Javascript ES5's forEach() method, although always return undefined, does execute side effects. For that reason, it is able to set the value of found to true once the item is equal to any of the obj properties. (Notice that forEach() continues to loop till the end of the array because it can't be stopped or broken unless there is an exception being thrown in).

So here I am, jotting this down hoping not to be fooled by a forEach() loop ever again.


Updated

Thanks to awesome recommendations by my dev fellows below I have levelled up my code which goes like so:

const haveCommonItems = (array1, array2) => {
    const array1set = new Set(array1);
    return array2.some(item => array1set.has(item));

haveCommonItems(animals, pets); // returns "true"
}
Enter fullscreen mode Exit fullscreen mode

Thanks again guys!

Discussion (13)

Collapse
gautierlepire profile image
Gautier Le Pire

Hi! You might be looking for a function named Array.some. It checks that at least one item in an array verifies a given predicate, and is early-terminating.

Here's the code using a hash table (so both arrays must contain only strings) as you did:

const haveCommonItems = (array1, array2) => {
    let hashTable = {}
    array1.forEach(item => hashTable[item] = true)
    return array2.some(item => hashTable[item])
}

:)

Collapse
kaynguyen profile image
Kay N. Author

Yes that sure does the job <3! Thanks for recommending!

Collapse
tbroyer profile image
Thomas Broyer

And you may want to use new Set(array1) instead of your hashTable.

const haveCommonItems = (array1, array2) => {
    const array1set = new Set(array1)
    return array2.some(array1set.has)
}

(disclaimer: untested code snippet)

Thread Thread
kaynguyen profile image
Kay N. Author

Awesome! Here's my tested code:

const haveCommonItems = (array1, array2) => {
    const array1set = new Set(array1);
    return array2.some(item => array1set.has(item));

haveCommonItems(animals, pets); // returns "true"
}

Very clean and time optimized! Thanks again everyone!

Collapse
clarity89 profile image
Alex K.

I prefer for.. of loop since it's more intuitive and has shorter syntax than the usual for loop.

BTW, Array#forEach is ES5 not ES6 feature ;)

Collapse
kaynguyen profile image
Kay N. Author

Definitely! Thanks for mentioning <3

Collapse
theosyslack_42 profile image
Theo Syslack

You might want to consider using Array.prototype.some. This will run a function against each element in an array, until something returns truthy. Then, it immediately stops and returns true. This will help a lot if you have a large number of items in the second array.
developer.mozilla.org/en-US/docs/W...

Collapse
kaynguyen profile image
Kay N. Author

Oh yeah totally forgot about that little guy. Thanks for mentioning! Will definitely use it next time similar needs come up!

Collapse
freebld profile image
Cristian

Hi, sorry I know it's just a code comment but in the first step when you iterate through the first array and tag items to true, your last output is dog. Should it not be kangaroo? 🤔
Thanks for this article👍

Collapse
kaynguyen profile image
Kay N. Author

Hey my bad - I totally forgot about the guy! (poor little kangaroo!). Already put it back in with the gang! Thanksss for pointing this out! <3

Collapse
iaboelsuod profile image
Ibrahim Zahema

I was thinking why foreach ALWAYS returns undefined by design. But in your use case it would have been better to use find & return whatever it returns. It can be chained and will break once the element is found.

Collapse
mike240892 profile image
Mike240892

Hi! Hace you considered using the .includes() method? If not, can you tell me why? I'm kind of new to JS.

Thanks for your article!

Collapse
kaynguyen profile image
Kay N. Author • Edited

Hi Mike yes you totally can use includes() however it would make the function not algorithm-wise because you gotta nest one loop inside another:

const haveCommonItems = (array1, array2) => {
  for (i = 0; i < array1.length; i++) {
    if (array2.includes(array1[i])) {
      return true;
    }
  }
};

haveCommonItems(animals, pets); // returns "true"

So you nest includes() which loops thru array2 inside forEach(), which is not ideal in terms of time complexity if you have a huge amount of items in your arrays.

Forem Open with the Forem app