Understanding Basics of map, filter, find functions by making your own [JavaScript]
Table of contents
- What we will cover
- Map
- Recreating output like map
- Filter
- Recreating output like filter
- Find
- Recreating output like find
- Recreating map like implementation with callbacks functions
- Recreating filter like implementation with callbacks functions
- Recreating find like implementation with callbacks functions
- Conclusion:
Let's get it straight, map, filter and find, saves some time by writing less code and delivering the right output. Understanding how it works will be helpful to implement it well.
What we will cover
In this article, we will explore how to write a simple version of it ourselves and in the process understand how exactly it works behind the hood. This article will be emphasizing getting the output with the help of a for
loop. The actual implementation of map
, filter
, find
involves a callback function, which we will also see and try to implement at the end.
Map
First, let's see the definition of map
function, according to MDN:
According to MDN:
The
map()
method creates a new array populated with the results of calling a provided function on every element in the calling array.
Let's break it down:
So here the map
takes an array and creates a new array and returns it.
But what does the new array hold? - results of calling a provided function on every element in the given array.
In other words, we are doing the same things to each element of the array and without changing the original array, we are returning a new array that has all the elements modified according to the instructions we provided in the function.
Let's see an example of what this is:
const numbers = [2,4,6,8,10];
Now suppose we want an array that has all the values of the given array and 6 added to each of its elements, then we can use map
for it.
changedArray = [8,10,12,14,16];
If there is a common operation to be performed on each of the elements of the array, we can use map
for it. Map
will perform the addition of 6 to each element and return everything in just one line of code.
const changedArray = number.map((numberElement)=>numberElement+6);
// output
[8,10,12,14,16]
Recreating output like map
Now let's try to recreate what map
did taking the above example. Since we want to work on each of the elements of the array. Let's use the for
loop to go through all the elements.
Note: Going through the same thing with the same repetitive action is also called iterating. Here we are iterating through the for
loop - performing a set of actions for every element.
STEP 1:
Question: How long should we iterate through the for loop?
Answer: As long as there are elements in the array.
Question: How do we know how many elements are there in the array?
Answer: using the array.length
property which will return the length of the array which is equivalent to the number of elements in the array here.
const numbers = [2,4,6,8,10];
for(let i=0; i<numbers.length; i++)
Good, now we are going to iterate over the array.
STEP 2:
Question: How do we access each element here in this iteration?
Answer: array[index] will help to access the element for each loop. Here i
is the index.
Question: What do we do, for each element in each iteration?
Answer: Add 6 to it.
const numbers = [2,4,6,8,10];
for(let i=0; i<numbers.length; i++)
{ numbers[i]+6; }
STEP 3:
Great, now we have added 6 in each iteration, but where do we store the new values?
Good question. We need to return a new array so let's create a new array before the for
loop and add this value to the new array.
const numbers = [2,4,6,8,10];
let changedArray = []; // new empty array
for(let i=0; i<numbers.length; i++)
{ numbers[i]+6; }
Notice here, we used the keyword let
for changedArray
. We can also use const
and it will not cause any issues while adding elements in the array.
const numbers = [2,4,6,8,10];
let changedArray = [];
for(let i=0; i<numbers.length; i++)
{ changedArray.push(numbers[i]+6); } // pushing values in new array
Note: There can be different ways to add elements to an array. We could also use changedArray[i] = number[i]+6;
So now we have added 6 to each element and also added it to the next array.
STEP 4:
Next, we need to return the changedArray
to see the changes made.
const numbers = [2,4,6,8,10];
let changedArray = [];
for(let i=0; i<numbers.length; i++)
{ changeArray.push(number[i]+6); }
return changedArray;
But we are returning to what?
Let's put it all in a function that can return it.
const numbers = [2,4,6,8,10];
function myMap() {
let changedArray = [];
for(let i=0; i<numbers.length; i++)
{ changeArray.push(number[i]+6); }
return changedArray;
}
STEP 5:
Wait for a second, how will myMap know what is numbers
? Shouldn't we let it know? As good practice in functional programming, we should write the function such that the output should be as expected. If we specify a defined array directly by its name for example numbers
here. It will use that only in the function call all the time. We don't want to use numbers
all the time, we may want to test it on some other array also with a different name, so we need to rename it to a generic name and also allow the function to take that generic name as a parameter, so it gives output only to that input.
Let us pass the parameter to the function - the generic array which needs to be iterated upon. Let's call it arrayReceived
since the function is receiving an array. Since we are changing the name, we need to also change it at other places to arrayReceived
from numbers
const numbers = [2,4,6,8,10];
function myMap(arrayReceived) {
let changedArray = [];
for(let i=0; i<arrayReceived.length; i++)
{ changedArray.push(arrayReceived[i]+6); }
return changedArray;
}
Now let's test our own map function if it works!
const numbers = [2,4,6,8,10];
function myMap (arrayReceived) {
let changedArray = [];
for(let i=0; i<arrayReceived.length; i++)
{ changedArray.push(arrayReceived[i]+6); }
return changedArray;
}
const myNewArray = myMap(numbers);
console.log("Hey my previous array was: " + numbers + "\n\nand results of my new array with my own map function is: " + myNewArray);
We can compare both functions and see the output below:
const numbers = [2,4,6,8,10];
function myMap (arrayRecieved) {
let changedArray = [];
for(let i=0; i<arrayRecieved.length; i++)
{ changedArray.push(arrayRecieved[i]+6); }
return changedArray;
}
const myNewArray = myMap(numbers);
console.log("Hey my previous array was:\n" + numbers + "\nand my new array without using inbuilt map function is:\n" + myNewArray);
const newArray = numbers.map((singleNumber)=> singleNumber+6);
console.log("Hey my previous array was:\n" + numbers + "\nand my new array using inbuilt map function is:\n" + myNewArray);
Here in the above example of map
, we assumed that the incremental value was 6. But let's say we wanted to take it as an external input, then the implementation would look like this:
const numbers = [2,4,6,8,10];
const numberG = 6;
function myMap (arrayReceived,numberG) {
let mappedArray = [];
for(let i=0; i<arrayReceived.length; i++)
{
mappedArray.push(arrayReceived[i]+numberG);
}
return mappedArray;
}
console.log(myMap(numbers,numberG))
console.log(numbers.map((singleNumber)=> singleNumber+numberG))
Here we stored the number in a variable numberG
and passed that as a parameter in the function and used its value for executing our condition in for loop.
So we recreated a simpler version of our own Map
function with the help of for
loop. Now let's try filter
.
Filter
According to MDN:
The
filter()
method creates a shallow copy of a portion of a given array, filtered down to just the elements from the given array that pass the test implemented by the provided function.
The filter
syntax is similar, and the logic is also quite similar to map
but here we return - part of the given array based on a specific condition to be performed on each element. We add the condition in the loop and return the element in the new array only if the condition is satisfied for that element.
Below we can see the difference in syntax for the map
and the filter
functions for the above example:
The code example below:
const numbers = [2, 4, 6, 8, 10];
const newArray = numbers.map((singleNumber) => singleNumber + 6);
const newArray2 = numbers.filter((element) => element > 6)
console.log("The original array is: \n" + numbers + "\n\nand the mapped array is incremented by 6:\n"+newArray+"\n\nand the filtered array checks if number is greater than 6:\n"+newArray2)
So we can see from the above example that the syntax difference is that filter
checks in the condition given to filter
is true or not and then only adds those elements into the new array.
Recreating output like filter
So here we do not perform the same action on all elements, we check if the element satisfies the condition given and then only push it to the new array.
let filteredArray = [];
for(let i=0; i<arrayReceived.length; i++)
{
if(arrayReceived[i]>6) // checks if condition is satisfied
filteredArray.push(arrayReceived[i]);
// adds to array if condition evaluates to true.
}
Now let's test our own filter function if it works!
const numbers = [2,4,6,8,10];
function myFilter (arrayReceived) {
let filteredArray = [];
for(let i=0; i<arrayReceived.length; i++)
{ if(arrayReceived[i]>6) // only this part changed for filter
filteredArray.push(arrayReceived[i]); // only this part changed for filter
}
return filteredArray;
}
const myNewArray = myFilter(numbers);
console.log("Hey my previous array was " + numbers + "\nand my new array without using inbuilt filter function is: " + myNewArray);
const newArray = numbers.filter((singleNumber)=> singleNumber>6);
console.log("Hey my previous array was: " + numbers + "\nand my new array using inbuilt filter function is: " + myNewArray);
Note: If no elements satisfy the condition, filter will return an empty array.
Find
According to MDN:
The
find()
method returns the first element in the provided array that satisfies the provided testing function. If no values satisfy the testing function,undefined
is returned.
It essentially means as soon as the testing condition is met, it returns that element. It does not check if other elements are satisfying the same criteria after it has found the first occurrence and if no elements satisfy the criteria it returns undefined
.
You could say, it filters the value but returns only the first value that meets the condition but here's a catch - additionally find
returns undefined
if no element has met the condition whereas filter
returns empty array.
const numbers = [2, 4, 6, 8, 10];
const numberG = 6;
console.log("Here is output from inbuilt filter function: "+numbers.filter((singleNumber) => singleNumber > numberG));
console.log("Here is output from inbuilt find function: "+numbers.find((singleNumber) => singleNumber > numberG));
Below example of returning undefined
by find
function and empty array
by filter
function when no condition is met:
const numbers = [2, 4, 6, 8, 10];
const numberG = 100;
console.log("Here is output from inbuilt filter function: "+numbers.filter((singleNumber) => singleNumber > numberG));
console.log("Here is output from inbuilt find function: "+numbers.find((singleNumber) => singleNumber > numberG));
Recreating output like find
So here we check if the element satisfies the condition given and then immediately return the element that satisfies the condition. And if no element satisfies the condition then we return undefined
.
for (let i = 0; i < arrayReceived.length; i++) {
if (arrayReceived[i] > numberG) return arrayReceived[i]; // find if element satisfies condition and return it. Don't check further
}
return undefined //return undefined after for loop execution.
Now let's test our own find function if it works!
const numbers = [2, 4, 6, 8, 10];
const numberG = 6;
function myFind(arrayReceived, numberG) {
for (let i = 0; i < arrayReceived.length; i++) {
if (arrayReceived[i] > numberG) return arrayReceived[i]; // find if element satisfies condition and return it. Don't check further
}
return undefined // if no element satisfies return undefined.
}
console.log("Here is output from my own find function: "+myFind(numbers, numberG));
console.log("Here is output from inbuilt find function: "+numbers.find((singleNumber) => singleNumber > numberG));
const numbers = [2, 4, 6, 8, 10];
const numberG = 100; // Changing value so that condition is not met.
function myFind(arrayReceived, numberG) {
for (let i = 0; i < arrayReceived.length; i++) {
if (arrayReceived[i] > numberG) return arrayReceived[i]; // find if element satisfies condition and return it. Don't check further
}
return undefined // if no element satisfies return undefined.
}
console.log("Here is output from my own find function: "+myFind(numbers, numberG));
console.log("Here is output from inbuilt find function: "+numbers.find((singleNumber) => singleNumber > numberG));
Recreating map
like implementation with callbacks functions
You can mention the callback
function inside the map
which will be an anonymous function
or you can define it outside and it will be a named function
. (Read more)
Map with anonymous callback function:
// example 01
// here you are defining the callback function inside.
// inside each iteration you are calling an anonymous function.
const numbers = [2,4,6,8,10];
const filterValues = numbers.map((number)=> number+6);
console.log(filterValues);
Implementation like "Map with anonymous callback function" means inside each iteration the function is called and the function exists only inside the map function. You won't be able to call the function which is outside the map.
const numbers = [2,4,6,8,10];
function myMap (arrayRecieved) {
const callbackFn = number => number + 6;
// defining callback function inside myMap itself.
// this function will be called for each iteration and executed. Works like how anonymous function would work.
let changedArray = [];
for(let i=0; i<arrayRecieved.length; i++)
{
const getValue = callbackFn(arrayRecieved[i]) // call function internally and store the changed value
changedArray.push(getValue) // add this stored value to the array.
}
return changedArray;
}
const myNewArray = myMap(numbers);
console.log("Hey my previous array was:\n" + numbers + "\nand my new array without using inbuilt map function is:\n" + myNewArray);
const newArray = numbers.map((singleNumber)=> singleNumber+6);
console.log("Hey my previous array was:\n" + numbers + "\nand my new array using inbuilt map function is:\n" + myNewArray);
Map with named callback function:
// example 02
// here you are defining the callback function outside.
// inside each iteration you are calling a defined function.
const numbers = [2,4,6,8,10];
const increment = (number)=> number+6;
const filterValues = numbers.map(increment);
console.log(filterValues);
Implementation like "Map with named callback function" means inside each iteration the function is called from outside. You will be able to call the function which is outside the map.
const numbers = [2,4,6,8,10];
const callbackFn = number => number + 6; ///this function will be called for each iteration and executed
function myMap (arrayRecieved, callbackFn) {
let changedArray = [];
for(let i=0; i<arrayRecieved.length; i++)
{
const getValue = callbackFn(arrayRecieved[i]);
changedArray.push(getValue) ;
} // for each iteration we are calling the function.
return changedArray;
}
const myNewArray = myMap(numbers,callbackFn);
console.log("Hey my previous array was:\n" + numbers + "\nand my new array without using inbuilt map function is:\n" + myNewArray);
const newArray = numbers.map((singleNumber)=> singleNumber+6);
console.log("Hey my previous array was:\n" + numbers + "\nand my new array using inbuilt map function is:\n" + myNewArray);
Recreating filter
like implementation with callbacks functions
Filter with anonymous callback function:
const numbers = [2,4,6,8,10];
const filterValues = numbers.filter((number)=> number>6);
console.log(filterValues);
Implementation like "Filter with anonymous callback function"
const numbers = [2,4,6,8,10];
function myFilter (arrayReceived) {
const callbackFn = number => number>6;
let filteredArray = [];
for(let i=0; i<arrayReceived.length; i++)
{
if( callbackFn(arrayReceived[i]) ) // only this part changed for filter
filteredArray.push(arrayReceived[i]); // only this part changed for filter
}
return filteredArray;
}
const myNewArray = myFilter(numbers);
console.log("Hey my previous array was " + numbers + "\nand my new array without using inbuilt filter function is: " + myNewArray);
const newArray = numbers.filter((singleNumber)=> singleNumber>6);
console.log("Hey my previous array was: " + numbers + "\nand my new array using inbuilt filter function is: " + myNewArray);
Filter with named callback function:
const numbers = [2,4,6,8,10];
const checkValue = number => number>6
const filterValues = numbers.filter(checkValue);
console.log(filterValues);
Implementation like "Filter with named callback function"
const numbers = [2,4,6,8,10];
const callbackFn = number => number>6;
function myFilter (arrayReceived, callback) {
let filteredArray = [];
for(let i=0; i<arrayReceived.length; i++)
{
if( callback(arrayReceived[i]) ) // only this part changed for filter
filteredArray.push(arrayReceived[i]); // only this part changed for filter
}
return filteredArray;
}
const myNewArray = myFilter(numbers,callbackFn);
console.log("Hey my previous array was " + numbers + "\nand my new array without using inbuilt filter function is: " + myNewArray);
const newArray = numbers.filter((singleNumber)=> singleNumber>6);
console.log("Hey my previous array was: " + numbers + "\nand my new array using inbuilt filter function is: " + myNewArray);
Recreating find
like implementation with callbacks functions
Find with anonymous callback function:
const numbers = [2,4,6,8,10];
const checkValue = number => number>6
const findValues = numbers.find((number) => number>6);
console.log(findValues);
Implementation like "Find with anonymous callback function"
const numbers = [2, 4, 6, 8, 10];
const numberG = 6;
function myFind(arrayReceived, numberG) {
const callbackFn = number => number>numberG;
for (let i = 0; i < arrayReceived.length; i++) {
if (callbackFn(arrayReceived[i])) return arrayReceived[i]; // find if element satisfies condition and return it. Don't check further
}
return undefined // if no element satisfies return undefined.
}
console.log("Here is output from my own find function: "+myFind(numbers, numberG));
console.log("Here is output from inbuilt find function: "+numbers.find((singleNumber) => singleNumber > numberG));
Find with named callback function:
const numbers = [2,4,6,8,10];
const checkValue = number => number>6
const findValues = numbers.find(checkValue);
console.log(findValues);
Implementation like "Find with named callback function"
const numbers = [2, 4, 6, 8, 10];
const numberG = 6;
const callbackFn = number => number>numberG;
function myFind(arrayReceived, callbackFn, numberG,) {
for (let i = 0; i < arrayReceived.length; i++) {
if (callbackFn(arrayReceived[i])) return arrayReceived[i]; // find if element satisfies condition and return it. Don't check further
}
return undefined // if no element satisfies return undefined.
}
console.log("Here is output from my own find function: "+myFind(numbers, callbackFn, numberG));
console.log("Here is output from inbuilt find function: "+numbers.find((singleNumber) => singleNumber > numberG));
Conclusion:
While making our map
, filter
, find
function may look easy, it is important to know that these functions may need to be modified to mimic the inbuilt functions which are robust enough to handle a variety of cases. But writing your implementation helps to understand the logic better.
Note: While all attempts were made to check and make this article factually correct, it may still have mistakes. If you would like to send feedback please write to me by sending a DM at twitter.com/swapnildecodes
Credits: Akash Kumar Singh for helping in the blog review and also Gautam Balamurali for his input.
Corrections: array.length
is property and not a function. Read here. it is corrected now. Thanks to Rohit for correcting.