Code smart! Be a faster, more productive, and happier JavaScript developer by mastering these most important and recurring functions in the language.
Whether it’s backend or frontend (or even spaceships), JavaScript is everywhere. It’s also quite a flexible language (meaning it’s got hardcore functional programming patterns as well as good ol’ classes), and its similarity to other “C-like” languages makes for an easy transition for developers from other languages.
If you want to level up your JS game, I suggest you learn about, practice, and eventually master the following core functions available in the language. Not all of these are strictly “needed” for solving problems, but they can do a lot of heavy lifting for you in some cases, while in others, these can reduce the amount of code you have to write.
map()
It’d be heresy to write an article on important JavaScript functions and not mention map()
! ๐๐ Along with filter()
and reduce()
, map()
forms a holy trinity of sorts. These are functions you’ll be using over and over in your career, so they’re more than worth a look. Let’s tackle them one by one, starting with map()
.
map()
is among those functions that give the most trouble to people learning JavaScript. Why? Not because there’s something inherently complex about it, but because the way this function works is an idea taken from what’s called Functional Programming. And since we’re not exposed to Functional Programming — our schools and the industry being full of Object-oriented languages — the workings seem strange or even wrong to our biased brains.
JavaScript is far more functional than Object-oriented, though its modern versions are doing their best to hide this fact. But that’s a whole can of worms that I can open perhaps some other day. ๐คฃ Okay, so, map()
. . .
map()
is a very simple function; it attaches itself to an array and helps us convert each item into something else, resulting in a new array. How exactly to convert an item is provided as another function, which by convention, is anonymous.
That’s all there is to it! The syntax might take some getting used to, but essentially that’s what we’re doing in a map()
function. Why might we want to use map()
? Depends on what we’re trying to achieve. For instance, let’s say we recorded the temperature for each day of the last week and stored it as a simple array. However, now we are told that the instruments were not very accurate and have reported 1.5 degrees less temperature than they should’ve.
We can do this correction using the map()
function like this:
const weeklyReadings = [20, 22, 20.5, 19, 21, 21.5, 23];
const correctedWeeklyReadings = weeklyReadings.map(reading => reading + 1.5);
console.log(correctedWeeklyReadings); // gives [ 21.5, 23.5, 22, 20.5, 22.5, 23, 24.5 ]
Another, very practical, example comes from the world of React, where creating DOM element lists from arrays is a common pattern; so, something like this is common:
export default ({ products }) => {
return products.map(product => {
return (
<div className="product" key={product.id}>
<div className="p-name">{product.name}</div>
<div className="p-desc">{product.description}</div>
</div>
);
});
};
Here, we have a functional React component that receives a list of products as its props. From this list (array), it then builds out a list of HTML “divs”, essentially converting every product object into HTML. The original products
object remains untouched.
You can argue that map()
is nothing but a glorified for
loop and you’d be totally right. But notice that as soon as you make that argument, it’s your Object-oriented trained mind that’s speaking, whereas these functions and their rationale come from Functional Programming, where uniformity, compactness, and elegance are highly revered. ๐
filter()
filter()
is a highly useful function that you’ll find yourself applying over and over in many situations. As the name suggests, this function filters an array based on the rules/logic you provide and returns a new array containing items that satisfy those rules.
Let’s reuse our weather example. Assume that we have an array containing the maximum temperatures for each day of last week; now, we want to find out how many of those days were colder. Yes, “colder” is a subjective term, so let’s say we’re looking for days where the temperature was below 20. We can do this using the filter()
function like this:
const weeklyReadings = [20, 22, 20.5, 19, 21, 21.5, 23];
const colderDays = weeklyReadings.filter(dayTemperature => {
return dayTemperature < 20;
});
console.log("Total colder days in week were: " + colderDays.length); // 1
Do note that the anonymous function we pass to filter()
must return a Boolean value: true
or false
. This is how filter()
will know whether or not to include that item in the filtered array. You’re free to write any amount of complex logic inside this anonymous function; you can make API calls and read user inputs, and so on, as long as you make sure that in the end, you’re returning a Boolean value.
Beware: This is a side note I feel compelled to provide based on my experience as a JavaScript developer. Whether it’s due to sloppiness or wrong fundamentals, many programmers create subtle bugs in their programs when using filter()
. Let’s rewrite the previous code to contain the bug:
const weeklyReadings = [20, 22, 20.5, 19, 21, 21.5, 23];
const colderDays = weeklyReadings.filter(dayTemperature => {
return dayTemperature < 20;
});
if(colderDays) {
console.log("Yes, there were colder days last week");
} else {
console.log("No, there were no colder days");
}
Notice something? Great job if you did! The if
condition towards the end checks colderDays
, which is actually an array! You’ll be surprised how many times people make this mistake as they race to meet deadlines or code in low spirits (for whatever reason). The problem with this condition is that JavaScript is a weird and inconsistent language in many ways, and the “truthiness” of things is one of them. While [] == true
returns false, making you think that the above code is not broken, the reality is that inside an if
condition, []
evaluates to true! In other words, the code we wrote will never say that there were no colder days last week.
The fix is very simple, as given in the code prior to the above code. We check for colderDays.length
, which is guaranteed to give us an integer (zero or above) and thus work consistently in logical comparisons. Note that filter()
will always, always, always return an array, empty or non-empty, so we can rely on that and write our logical comparisons confidently.
It’s been a longer detour than I was planning, but bugs like this one are worth highlighting in ten thousand words, all caps if need be. I hope you don’t get bitten by this and save yourself hundreds of hours of debugging effort! ๐
reduce()
Of all the functions in this article, as well as in the standard JavaScript library, reduce()
is among the frontrunners for the crowns of “confusing and weird”. While this function is highly important and results in elegant code in many situations, it’s avoided by most JavaScript developers and they prefer to write more verbose code instead.
The reason is that — and I’ll be honest here! — reduce()
is hard to understand, in the sense of both concept and execution. When you read its description, you have re-read it several times and still, you doubt yourself whether you read it wrong; and when you see it in action and try to visualize how it’s working, your brain twists into a thousand knots! ๐คญ
Now, don’t get scared. The reduce()
function is nowhere close in complexity and intimidation to, say, B+ Trees and their algorithms. It’s just that this sort of logic is rarely encountered during the average programmer’s day job.
So, having scared the daylights out of you and then immediately telling you to not worry, I’d finally like to show you what this function is and why exactly we might need it.
As the name suggests, reduce()
is used to, well, reduce something. The thing it reduces is an array and the thing it reduces the given array to is a single value (number, string, function, object, whatever). Here’s a simpler way to put it — reduce()
transforms an array into a single value. Note that the return value from reduce()
is not an array, which is the case with map()
and filter()
. Having understood this much is half the battle already. ๐
Now, it’s kinda obvious that if we’re going to transform (reduce) an array, we need to supply the necessary logic; and based on your experience as a JS dev, you’ve most like already guessed that we do that using a function. This function is what we call the reducer function, which forms the first argument to reduce()
. The second argument is a starting value, such as a number, a string, etc. (I’ll explain in a while what the heck this “starting value” is).
Based on our understanding so far, we can say that a call to reduce()
looks like this: array.reduce(reducerFunction, startingValue)
. Now let’s tackle the heart of the whole thing: the reducer function. As already established, the reducer function is what tells reduce()
how to convert the array into a single value. It takes two arguments: a variable to act as an accumulator (don’t worry, I’ll explain this bit as well), and a variable to store the current value.
I know, I know . . . that was a lot of terminology for a single function that is not even mandatory in JavaScript. ๐๐ And this is why people run away from reduce()
. But if you learn it step-by-step, you’ll not only understand it but also appreciate it as you become a better developer.
Okay, so, back to the topic at hand. The “starting value” being passed to reduce()
is . . . well, the starting value for the calculation you want to use. For example, if you’re going to do multiplication in the reducer function, a starting value of 1
makes sense; for addition, you might start with 0
, and so on.
Now let’s look at the signature for the reducer function. A reducer function being passed to reduce()
has the following form: reducerFunction(accumulator, currentValue)
. “Accumulator” is just a fancy name for the variable that collects and holds the result of the calculation; it’s exactly like using a variable called total
to sum all the items in an array using something like total += arr[i]
. This is exactly how the reducer function in reduce()
gets applied: the accumulator is initially set to the starting value you provide, and then one by one the elements in the array are visited, the calculation is performed, and the result is stored in the accumulator, and so on . . .
So, what’s this “current value” in a reducer function? It’s the same idea that you’d mentally picture if I ask you to traverse an array: you’d take a variable to start at index zero and move it forward a step at a time. While you’re doing this, if I ask you to suddenly stop, you’d find yourself on one of the elements of the array, right? This is what we mean by current value: it’s the value of the variable used to represent the array item that is currently under consideration (think of looping over an array if that helps).
With all that said, it’s time to see a simple example and see how all this jargon comes together in an actual reduce()
call. Let’s say we have an array holding the first n
natural numbers (1, 2, 3 . . . n
) and we’re interested in finding the factorial of n
. We know that to find n!
we simply need to multiply everything, which leads us to this implementation:
const numbers = [1, 2, 3, 4, 5];
const factorial = numbers.reduce((acc, item) => acc * item, 1);
console.log(factorial); // 120
A lot is going on in these mere three lines of code, so let’s unpack it one by one in the context of the (very long) discussion we’ve had till now. As is obvious, numbers
is the array that’s holding all the numbers we want to multiply. Next, have a look at the numbers.reduce()
call, which says that the starting value for acc
should be 1
(because it doesn’t influence or destroy any multiplication). Next, check the reducer function body, `(acc, item) => acc * item
, which simply says that the return value for each iteration over the array should be that item multiplied by whatever’s already in the accumulator. The iteration and actually storing the multiplication explicitly in the accumulator is what’s happening behind the scenes, and is one of the biggest reasons reduce()
is such a stumbling block for JavaScript developers.
Why use reduce()
?
It’s a really great question and to be honest, I don’t have a sure-shot answer. Whatever reduce()
does can be done through loops, forEach()
, etc. However, those techniques result in much more code, making it hard to read, especially if you’re in a hurry. Then there’s the concern for immutability: with reduce()
and similar functions, you can be sure that your original data hasn’t been mutated; this in itself eliminates entire classes of bugs, especially in distributed applications.
Finally, reduce()
is far more flexible, in the sense that the accumulator can be an object, an array, or even a function if needed; the same goes for the starting value and other parts of the function call — almost anything can go in, and almost anything can come out, so there’s extreme flexibility in designing reusable code.
If you’re still not convinced, that’s perfectly okay too; the JavaScript community itself is sharply divided over the “compactness”, “elegance”, and “power” of reduce()
, so it’s okay if you don’t use it. ๐ But do make sure you look at some neat examples before you decide to bin reduce()
.
some()
Let’s say you have an array of objects, with each object representing a person. You want to know whether there are people in the array who are over the age of 35. Note that there’s no need to count how many such people are, let alone retrieve a list of them. What we’re saying here is the equivalent of “one or more” or “at least one”.
How do you do this?
Yes, you could create a flag variable and loop over the array to solve this problem like this:
const persons = [
{
name: 'Person 1',
age: 32
},
{
name: 'Person 2',
age: 40
},
];
let foundOver35 = false;
for (let i = 0; i < persons.length; i ++) {
if(persons[i].age > 35) {
foundOver35 = true;
break;
}
}
if(foundOver35) {
console.log("Yup, there are a few people here!")
}
The problem? The code is too C-like or Java-like, in my opinion. “Verbose” is another word that comes to mind. Experienced JS might be thinking of “ugly”, “horrible”, etc. ๐ And rightly so, I’d argue. One way to improve this piece of code is to make use of something like map()
, but even then the solution is a bit clunky.
Turns out we have a rather neat function called some()
already available in the core language. This function works arrays and accepts a custom “filtering” function, returning a Boolean value of true
or false
. Essentially, it’s doing what we’ve been trying to do for the last few minutes, only very succinctly and elegantly. Here’s how we can use it:
const persons = [
{
name: 'Person 1',
age: 32
},
{
name: 'Person 2',
age: 40
},
];
if(persons.some(person => {
return person.age > 35
})) {
console.log("Found some people!")
}
Same input, the same result as before; but notice the massive reduction in code! Notice also how drastically the cognitive burden is reduced because we no more need to parse the code line by line as if we were the interpreter ourselves! The code now almost reads like natural language.
every()
Just like some()
, we have another useful function called every()
. As you can guess by now, this too returns a Boolean value depending on whetherย all the items in the array pass the given test. Of course, the test to pass is supplied as an anonymous function most of the time. I’ll spare you the pain of what a naive version of the code might look like, so here’s how every()
is used:
const entries = [
{
id: 1
},
{
id: 2
},
{
id: 3
},
];
if(entries.every(entry => {
return Number.isInteger(entry.id) && entry.id > 0;
})) {
console.log("All the entries have a valid id")
}
As is obvious, the code checks all the objects in the array for a valid id
property. The definition of “valid” depends on the problem context, but as you can see, for this code, I considered non-negative integers. Once again, we see how simple and elegant the code is to read, which is the sole aim of this (and similar) function(s).
includes()
How do you check for the existence of sub-strings and array elements? Well, if you’re like me, you quickly reach for indexOf()
and then look up the docs to make you know its possible return values. It’s a sizeable inconvenience, and the return values are hard to remember (quick — what does a process returning 2
to the operating system mean?).
But there’s a nice alternative that we can make use of: includes()
. The usage is as simple as the name, and the resulting code is extremely heart-warming. Do keep in mind that the matching is done by includes()
is case-sensitive, but I guess that’s what we all intuitive expect anyway. And now, time for some code!
const numbers = [1, 2, 3, 4, 5];
console.log(numbers.includes(4));
const name = "Ankush";
console.log(name.includes('ank')); // false, because first letter is in small caps
console.log(name.includes('Ank')); // true, as expected
However, don’t expect too much from this humble method:
const user = {a: 10, b: 20};
console.log(user.includes('a')); // blows up, as objects don't have a "includes" method
It can’t look inside objects as it’s simply not defined for objects. But hey, we do know it works on arrays, so maybe we can do some trickery here . . . ๐ค.
const persons = [{name: 'Phil'}, {name: 'Jane'}];
persons.includes({name: 'Phil'});
So, what do you happens when you run this code? It doesn’t explode, but the output is disappointing as well: false
. ๐ซ๐ซ Actually, this has to do with objects, pointers, and how JavaScript sees and manages memory, which is a world of its own. If you wish to dive deeper, feel free to take the plunge (maybe start here), but I’ll stop right here.
We can make the above code behave if we rewrite it as follows, but at this point, it more or less becomes a joke, in my opinion:
const phil = {name: 'Phil'};
const persons = [phil, {name: 'Jane'}];
persons.includes(phil); // true
Still, it does show that we can make includes()
works on objects, so I guess it’s not a total disaster. ๐
slice()
Suppose we have a string, and I ask you the return a part of it that starts with “r” and ends with “z” (the actual characters are not important). How would you approach it? Perhaps you’d create a new string and use it to store all the necessary characters and return them. Or if you’re like most programmers, you’d give me two array indices in return: one indicating the start of the substring, the other marking the end.
Both these approaches are fine, but there’s a concept called slicing that offers a neat solution in such situations. Thankfully, there’s no abstruse theory to follow; slicing means exactly what it sounds like — creating a smaller string/array from the given one, much like we create slices of fruits. Let’s see what I mean, with the help of a simple example:
const headline = "And in tonight's special, the guest we've all been waiting for!";
const startIndex = headline.indexOf('guest');
const endIndex = headline.indexOf('waiting');
const newHeadline = headline.slice(startIndex, endIndex);
console.log(newHeadline); // guest we've all been
When we slice()
, we provide two indices to JavaScript — the one where we want to start the slicing, and the other where we want it to stop. The catch with slice()
is that the ending index isย not includedย in the final result, which is why we see that the word “waiting” is missing from the new headline in the code above.
Concepts like slicing are more prominent in other languages, notably Python. If you ask those developers, they’ll say they can’t imagine life without this functionality, and rightly so when the language provides a very neat syntax for slicing.
Slicing is neat and extremely convenient, and there’s no reason to not use it. It’s also not syntax sugar riddled with a performance penalty, as it creates shallow copies of the original array/string. For JavaScript developers, I highly recommended getting familiar with slice()
and adding it to your arsenal!
splice()
The method splice()
sounds like a cousin of slice()
, and in some ways, we can argue that it is. Both create new arrays/strings from the original ones, with one small but important difference — splice()
removes, changes, or adds elements but modifies the original array. This “destruction” of the original array can create huge problems if you’re not careful or don’t understand deep copies and references. I wonder what was stopping the devs from using the same approach as for slice()
and leave the original array untouched, but I guess we can be more forgiving towards a language created in merely ten days.
My complaints notwithstanding, let’s take a look at how splice()
works. I’ll show an example where we remove a few elements from an array, as this is the most common use you’ll find for this method. I’ll also refrain from providing examples of addition and insertion because these can be easily looked up and are simple as well.
const items = ['eggs', 'milk', 'cheese', 'bread', 'butter'];
items.splice(2, 1);
console.log(items); // [ 'eggs', 'milk', 'bread', 'butter' ]
The call to splice()
above says: start at index 2
(the third place, that is) of the array, and remove one item. In the given array, ‘cheese’ is the third item, so it gets removed from the array and the array of items gets shortened, as expected. By the way, the items removed get returned by splice()
in the form or an array, so if we wanted to, we could have captured ‘cheese’ in a variable.
In my experience, indexOf()
and splice()
have great synergy — we find the index of an item and then remove it from the given array. However, note that it’s not the most efficient method always, and often using an object (the equivalent of a hash map) keys is much faster.
shift()
shift()
is a convenience method of sorts and is used to remove the first element of an array. Notice that the same thing can be done with splice()
, but shift()
is a bit easier to remember and intuitive when all you need to do is chop off the first element.
const items = ['eggs', 'milk', 'cheese', 'bread', 'butter'];
items.shift()
console.log(items); // [ 'milk', 'cheese', 'bread', 'butter' ]
unshift()
Just as shift()
removes the first element from an array, unshift()
adds a new element to the start of the array. Its use is just as simple and compact:
const items = ['eggs', 'milk'];
items.unshift('bread')
console.log(items); // [ 'bread', 'eggs', 'milk' ]
That said, I can’t help myself and warn those new to the game: unlike the popular push()
and pop()
methods, shift()
and unshift()
are extremely inefficient (because of the way the underlying algorithms work). So, if you’re operating on large arrays (say, 2000+ items), too many of these function calls can grind your application to a halt.
fill()
Sometimes you need to change several items to a single value or even “reset” the whole array, so to speak. In those situations, fill()
saves you from loops and off-by-one errors. It can be used to replace some or all of the array with the given value. Let’s see a couple of examples:
const heights = [1, 2, 4, 5, 6, 7, 1, 1];
heights.fill(0);
console.log(heights); // [0, 0, 0, 0, 0, 0, 0, 0]
const heights2 = [1, 2, 4, 5, 6, 7, 1, 1];
heights2.fill(0, 4);
console.log(heights2); // [1, 2, 4, 5, 0, 0, 0, 0]
Other functions worth mentioning
While the above list is what most JavaScript developers end up encountering and using in their careers, it’s by no means complete. There are so many more minor but useful functions (methods) in JavaScript that it won’t be possible to cover them all in a single article. That said, a few that come to mind are as follows:
reverse()
sort()
entries()
fill()
find()
flat()
I encourage you to at least look these up so that you have some idea that conveniences like these exist.
Conclusion
JavaScript is a large language, despite the small number of core concepts to learn. The many functions (methods) available to us make up the bulk of this large size. However, since JavaScript is a secondary language to most developers, we don’t dive deep enough, missing out on many beautiful and useful functions it offers. Actually, the same goes for functional programming concepts, but that’s a topic for another day! ๐
Whenever you can, spend some time exploring the core language (and if possible, famous utility libraries such as Lodash). Even a few minutes spent making this effort will result in massive productivity gains and far cleaner and more compact code.