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"
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
);
}
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}
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!")
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 valueundefined
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!")
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"
}
Thanks again guys!
Top comments (13)
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:
:)
Yes that sure does the job <3! Thanks for recommending!
And you may want to use
new Set(array1)
instead of yourhashTable
.(disclaimer: untested code snippet)
Awesome! Here's my tested code:
Very clean and time optimized! Thanks again everyone!
I prefer
for.. of
loop since it's more intuitive and has shorter syntax than the usualfor
loop.BTW,
Array#forEach
is ES5 not ES6 feature ;)Definitely! Thanks for mentioning <3
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...
Oh yeah totally forgot about that little guy. Thanks for mentioning! Will definitely use it next time similar needs come up!
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π
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
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.
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!
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:So you nest
includes()
which loops thru array2 insideforEach()
, which is not ideal in terms of time complexity if you have a huge amount of items in your arrays.