Understanding Basics of map, filter, find functions by making your own [JavaScript]

Understanding Basics of map, filter, find functions by making your own [JavaScript]

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


Are You Ready Ready GIF - Are You Ready Ready Ignace Aleya GIFs


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

Math Zack Galifianakis GIF - Math Zack Galifianakis Thinking GIFs


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

Rocket Science Complicated GIF - Rocket Science Complicated Difficult GIFs


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

Hooray We Did It GIF - Hooray We Did It GIFs


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.

Resources: map, filter and find.