DEV Community

loading...
Cover image for The Most POWERFUL [JavaScript] Function

The Most POWERFUL [JavaScript] Function

Clean Code Studio
Clean Code Clean Life ~ Simplify
Updated on ・11 min read
cleancodestudio image

Twitter Follow

Did you know I have a newsletter? 📬

If you want to get notified when I publish new blog posts or
make major project announcements, head over to
https://cleancodestudio.paperform.co/


The most POWERFUL [JavaScript] Function


"Today you learn how to use **THE MOST POWERFUL** JavaScript Function out there."


Array.reduce


"Array.reduce is THE MOST POWERFUL JavaScript Function. PERIOD."

  • Yah - okay, you got me. Technically this is my opinion.
  • That being said, I full-heartedly believe in this opinion.
  • Yep, after this post, I'm hoping you share this opinion too!

At the end of this article, I openly invite you to challenge this opinion of mine in the comments section. I'm down for a comments war that shares contradicting perspectives :)

With that being said, let's dive in!


Article Structure & Related Resources


Article Structure

  • Easy examples
    • Are at the beginning of the article
    • Are not intended to be real world use cases
    • Are meant to teach how to utilize reduce simply
    • Are intended to reduce by replacing functions with reduce
    • Can skip if you already understand how to implement reduce
  • Intermediate examples
    • Are after the easy examples
    • Are intended to show some real world use cases
    • Are not intended to explain the most powerful parts of reduce
  • Advanced examples
    • Found after the intermediate examples
    • Intended to show real world use cases
    • Intended to explain more powerful real-world reduce use cases
  • Expert and rarely talked about reduce Tips/Tricks
    • All of, Array.reduce's accumulator callback parameters
    • How to break out of Array.reduce's loops similar to break
    • How to mutate the original source array reduce has access to

Related Resources: Array.Reduce YouTube Video

  • Video code example walk throughs
  • YouTube video and this article have very similar content
  • Note: It's simpler to understand some examples via video


Array.reduce


What does reduce do? Why is it so powerful?

Well, here's the technical definition of reduce...

Array.prototype.reduce()


The reduce() method executes a reducer function (that you provide) on each element of the array, resulting in a single output value.

Not very helpful if you ask me, so LET'S LEARN BY DOING


1. Find total of all numbers summed (using reduce)



[3, 2.1, 5, 8].reduce((total, number) => total + number, 0)

// loop 1: 0 + 3
// loop 2: 3 + 2.1
// loop 3: 5.1 + 5
// loop 4: 10.1 + 8
// returns 18.1

Enter fullscreen mode Exit fullscreen mode

2. Find total of all numbers multiplied


[3, 2.1, 5, 8].reduce((total, number) => total * number, 1)
Enter fullscreen mode Exit fullscreen mode

As shown, it's pretty easy to add or multiply all of the numbers in an array. But, c'mon - I said reduce is THE MOST POWERFUL FUNCTION IN ALL OF JS.

Obviously, there has to be more to reduce - right?

Let's say we have 3, 4, 10, and 60. Let's say we want to get some of the values.

Or, more specifically, we want to filter the values and only return the values if they are less than 10.

Normally, we can simply use the filter function and only return the numbers if they are less than 10.


3. Array.Filter using reduce


[3, 4, 10, 60].filter(number => number < 10)
Enter fullscreen mode Exit fullscreen mode

Well, with reduce - we can do the exact same thing.

[3, 4, 10, 60].reduce((list, number) => 
   number < 10
      ? [...list, number]
      : list
, [])
Enter fullscreen mode Exit fullscreen mode

And lala, we replaced filter with reduce - pretty cool but let's be honest. This STILL DOESN'T justify reduce as being THE MOST POWERFUL FUNCTION IN ALL OF JavaScript.


What if I told you, we could continue down this path and replace just about every array function in JavaScript using reduce?



3. Re-creating Array.some using Array.reduce


[3, 4, 10, 50].some(number => number < 50)

// returns true (We have some numbers in the array are less than 50)
Enter fullscreen mode Exit fullscreen mode

Using reduce, we simply set the initial value to false. If the condition is already true then we return the condition. If the condition is not already true then we check if the current number meets our condition.

[3, 4, 10, 50].reduce((condition, number) => 
   condition === true  
       ? condition
       : number < 50
, false)
Enter fullscreen mode Exit fullscreen mode

Notice, this time we start with a bool (false) as our initial value instead of using a number or an array.


We have now summed, multiplied, filtered, and re-created some (aka conditionally checking something on our array using reduce).

We could go on to also replace the Array.every function using Array.reduce, but since that is similar to replacing Array.some using Array.reduce we'll just note that we can easily do that as well.


4. What about Array.join using Array.reduce?


Replacing Array.join with Array.reduce

['truck', 'car', 'people'].join('-')

// "truck-car-people"
Enter fullscreen mode Exit fullscreen mode

Using Array.reduce we can code the following

['truck', 'car', 'people'].reduce((text, word) => `${text}-${word}`, '')

// "-truck-car-people"
Enter fullscreen mode Exit fullscreen mode

Notice the output has a preceding dash at the front.

The callback function accepted as the first argument for the Array.reduce function accepts more parameters. We can use the third accepted parameters to track our index for our reduce function

['truck', 'car', 'people'].reduce((text, word, index) => 
    index === 0
       ? word 
       : `${text}-${word}`
, '')

// "truck-car-people"
Enter fullscreen mode Exit fullscreen mode

With that 3rd parameter set up, this reduce function will now act exactly how the original Array.join acts


With that, so far we have used reduce to replace.

  • Array.map
  • Array.filter
  • Array.every, Array.some
  • Array.join

5. Array.concat using Reduce


What about concat? Where you can concat an array of "1", "2", and "3" with another array?

[1, 2, 3].concat(['hey', 'world', 'mars'])

// [1, 2, 3, 'hey', 'world', 'mars']
Enter fullscreen mode Exit fullscreen mode

How would you concat or combine arrays reduce?

[[1,2,3], ['hey', 'world', 'mars']].reduce(
   (list, array) => [...list, ...array],
[])

// [1, 2, 3, 'hey, 'world', 'mars']
Enter fullscreen mode Exit fullscreen mode

What's cool about combining arrays using Array.reduce is that we can "concat" as many arrays as we want to.

Simply, by passing in more arrays we will automatically combine aka concatenate them using reduce.

With that, we have replicated Array.concat using Array.reduce


Let's get into a few more examples.

First, let's create a few people.

let sarah = { name: 'sarah', email: 'sarah@gmail.com', id: 1 }
let tim = { name: 'tim', email: 'tim@gmail.com', id: 2 }
let len = { name: 'len', email: 'len@gmail.com', id: 3 }
Enter fullscreen mode Exit fullscreen mode

6. Grouping People By Names using Array.reduce


Example of what we want when we group people by names

people.len

// Gets Len
// { name: 'len', email: 'len@gmail.com', id: 3 }


people.sarah

// Gets sarah
// { name: 'sarah', email: 'sarah@gmail.com', id: 1}
Enter fullscreen mode Exit fullscreen mode

Grouping people by their names using reduce

  • Make the initial value for our reduce function an object
  • Build an object where
    • The key is the person's name ([person.name])
    • The value is the person object ([person.name]: person)

Example (That won't work)

let people = [sarah, tim, len].reduce((people, person) => {
   [person.name]: person,
   ...people
}, {}) 
Enter fullscreen mode Exit fullscreen mode

In the example above we'll get an error

Uncaught SyntaxError: Unexpected token ':'

Whenever we use a short hand function to return an object we need to wrap it in parentheses

  • Wrap the returned object's brackets in parentheses to fix the error
let people = [sarah, tim, len].reduce((people, person) => ({
   [person.name]: person,
   ...people
}), {}) 
Enter fullscreen mode Exit fullscreen mode

And lala, we now have a people object where the people are grouped by their name

If we go people.len we get len

people.len // { name: 'len', email: 'len@gmail.com', id: 3 }
Enter fullscreen mode Exit fullscreen mode

If we go people.sarah we get sarah

people.sarah // { name: 'sarah', email: 'sarah@gmail.com', id: 1 }
Enter fullscreen mode Exit fullscreen mode

If we go people.tim we get tim

people.tim // { name: 'tim', email: 'tim@gmail.com', id: 2 }
Enter fullscreen mode Exit fullscreen mode

If we want all of our people?

// people 
{
   sarah: { name: 'sarah', email: 'sarah@gmail.com', id: 1 },
   tim: { name: 'tim', email: 'tim@gmail.com', id: 2 },
   len: { name: 'len', email: 'len@gmail.com', id: 3 },

}
Enter fullscreen mode Exit fullscreen mode

7. Plucking an array of values by a given key using Reduce


More than that, what if we wanted to get just the names of the people?

let names = [sarah, tim, len].reduce((names, person) => [
   ...names,
   person.name
], [])

// ['sarah', 'tim', 'len']
Enter fullscreen mode Exit fullscreen mode

What if we wanted to get just the emails of the people?

let emails = [sarah, tim, len].reduce((emails, person) => [
   ...emails,
   person.email
], [])

// ['sarah@gmail.com', 'tim@gmail.com', 'len@gmail.com']
Enter fullscreen mode Exit fullscreen mode

8. Flattening multiple levels of nested Arrays using Reduce


More than that, what if we had an array of nested arrays?

let list_of_arrays = [
    ['sub_one', 'sub_two', 'sub_three'],
    [
       ['nested_sub_one', 'nested_sub_two'], 
       ['nested_sub_three', 'nested_sub_four']
    ],
    'one',
    'two',
    'three'
]
Enter fullscreen mode Exit fullscreen mode

Let's take our list of arrays, and lets of course use reduce

list_of_arrays.reduce((flattened, item) => {
   if (Array.isArray(item) === false) {
      return [...flattened, item]
   }
   if (Array.isArray(item) && Array.isArray(item[0])) {
      return [
         ...flattened,
         ....item.reduced((flatten, nested_list) => [...flatten, ...nested_list, [])
       ]
      ]
   }

   return [...flattened, ...item]
}, [])
Enter fullscreen mode Exit fullscreen mode

And lala, we've flattened our list of multiple level nested arrays.

Output

["sub_one", "sub_two", "sub_three", "nested_sub_one", "nested_sub_two", "nested_sub_three", "nested_sub_four", "one", "two", "three"]
Enter fullscreen mode Exit fullscreen mode

Note:

We only handled nested sub arrays up to 3 levels deep, but you could of course spend some more time on the function and use recursion to pretty simply flatten an array infinite nested levels deep using reduce.


More POWERFUL use cases for Reduce


Alright, so now let's dive into some of the more Powerful, not as oftenly used - use cases for Array.reduce.


9. Apply Formatters on Strings


I'm going to start off with an array of strings.

let strings = ['cool-link', 'hello world of javascript', 'goodbye, its been swell']
Enter fullscreen mode Exit fullscreen mode

Next let's create an array of formatters. Normally, I'd call these filters - but they're not really filters. They're just formatting the string.

These formatters are actually going to be callback functions.

First, we'll create a dashes to spaces formatter (replace dashes with spaces). Will use regex to implement this formatter.

let dashesToSpaces = str => str.replace(/-/g, ' ')
Enter fullscreen mode Exit fullscreen mode

Next, we'll create a capitalize string formatter.

let capitalize = str => `${str[0].toUpperCase()}${str.slice(1)}`
Enter fullscreen mode Exit fullscreen mode

Then, we'll create a string limiter formatter.

If the string is greater than a given length, replace the characters after that length limit with three dots.

let limiter = str => str.length > 10 ? `${str.slice(0, 10)}...` : str 
Enter fullscreen mode Exit fullscreen mode

Finally, we'll create a formatters array with all of our string formatters.

let formatters = [dashesToSpaces, capitalize, limiter]
Enter fullscreen mode Exit fullscreen mode

Remember we have our array of strings.

let strings = ['cool-link', 'hello world of javascript', 'goodbye, its been swell']
Enter fullscreen mode Exit fullscreen mode

Our Goal:

Our goal is to apply every single formatter from our formatters array onto every single string from our strings array.

Using reduce, we can simply do this like so!

strings.reduce((list, str) => [
      formatters.reduce((string, format) => format(string), str),
      ...list
   ],
[])
Enter fullscreen mode Exit fullscreen mode

_And just like that, we used reduce to apply an array of formatters on an array of strings. _

Original Strings Array

['cool-link', 'hello world of javascript', 'goodbye, its been swell']
Enter fullscreen mode Exit fullscreen mode

Output (After using reduce to apply string formatters)

["Goodbye, i...", "Hello worl...", "Cool link"]
Enter fullscreen mode Exit fullscreen mode

10. Group students by rooms (using reduce)


First let's create some students

let students = [
   { name: 'Sally', room: 'A' },
   { name: 'tim', room: 'A' },
   { name: 'nick', room: 'B' },
   { name: 'rick', room: 'C' },
   { name: 'sarah', room: 'B' },
   { name: 'pam', room: 'C' }
]
Enter fullscreen mode Exit fullscreen mode

We want to group the students by their room

So what we're going to do is use students.reduce.

students.reduce((class_rooms, student) => ({
}), {})
Enter fullscreen mode Exit fullscreen mode

Notice we use the parentheses around the object we're implicitly returning again. When we use a short hand function to return an object we have to use ({}) syntax - if we attempt to directly return an object without the wrapping () we'll get an error.

Next, we want to use the student room as the key:

students.reduce((rooms, student) => ({
   ...rooms,
   [student.room]: rooms[student.room]
        ? [...rooms[student.room], student]
        : [student]
}), {})
Enter fullscreen mode Exit fullscreen mode

Now, we have our students grouped by their rooms/classes.

{
   A: [{ name: 'sally', room: 'A' }, { name: 'tim', room: 'A' }],
   B: [{ name: 'nick', room: 'B' }, { name: 'sarah', room: 'B'}],
   C: [{ name: 'rick', room: 'C' }, { name: 'pam', room: 'C' }],
}
Enter fullscreen mode Exit fullscreen mode

We have successfully grouped our students by their rooms - so that is how we group by reduce.


So guys, that's about all I've got with reduce. I guess the biggest takeaway is that reduce is a super method - it really is!

You can do just about anything you can do with any other Array method using reduce.

Instead of going Array.filter.map.filter.forEach, you could use a single reduce function to accomplish the same goal.

If you need to group a whole bunch of object by their keys, use reduce.

If you need to pluck the values related to a given key? Use reduce.

If you need to apply multiple filters but don't want to raise the time complexity by iterating multiple times over through the same array - use reduce.

If you want to flatten an array of nested arrays where each nested array may have more nested array while each nested array also may not have any nested arrays? Use reduce.

If you need to sum some number, multiply some numbers, subtract sum numbers, or do some arithmetic of any sort - reduce works again.

What if you need to combine some arrays? Use reduce.

What if you need to combine some objects? Use reduce.

What if you want to have a method in your back pocket that you know can do it all and just makes you feel more powerful and efficient as a software engineer?

Use reduce!

In my opinion, forEach is the most over rated method in the JavaScript eco-system and reduce is the most under rated method in the JS eco-system.


As a final example of how cool reduce is let's take this final example.

[{ name: 'Clean Code Studio' }, { belief: 'Simplify!' }, { should_follow: 'Si, senor!' }].reduce((last_example, partial) => ({ 
   ...last_example, ...partial }), {})

Enter fullscreen mode Exit fullscreen mode

What does this return? It merges all of the objects.

{

   name: 'Clean Code Studio',
   belief: 'Simplify',
   should_follow: 'Si, senor!'
}
Enter fullscreen mode Exit fullscreen mode
cleancodestudio image

Using reduce you can filter, you can apply, you can apply a list of callbacks, you can flatten, merge, combine...

I highly recommend that you become familiar, competent, and over all familiar when it comes to using reduce.

So again, using reduce you have two parameters.

  • accumulator - callback function
  • initial value - used during the first iteration by the accumulator callback function
[].reduce(accumulatorCallbackFunction, initialValue)
Enter fullscreen mode Exit fullscreen mode

The Accumulator callback function has four parameters

  • accumulator - the value returned from the callback function after each iteration
  • item - element from the array
  • index - the index for the current element being passed into the accumulator callback
  • source - the original array reduce is being called on
let initial = []
let callback = (accumulator, item, index, source) => {}

[].reduce(callback, initial)
Enter fullscreen mode Exit fullscreen mode

Finally, the last bonus tip - what if you want to break out of reduce before you're done iterating through all of the items?


[].reduce((build, item, index, source) => source.slice(index), 0)

Enter fullscreen mode Exit fullscreen mode

By slicing the source array at the given index, you'll break out of the reduce functions loop - thus if you have a big data set, you can stop using computational resources once a condition is met.


With that, I'll close out by saying I highly recommend practicing reduce. It is the JavaScript function that I have found the absolute most use out of. So many times, reduce has been the solution to solving complex coding challenges in a concise and to the point way.

For demo's on every single Array.reduce concept we covered here. Checkout the screencast I created - we dive deep into reduce. We start from simple and build up to eventually cover all of the examples shared in this post.

Thanks for tuning in, and if you have any comments - questions - or concerns, the comment section is right below :)

Clean Code Studio
Clean Code Clean Life
Design Patterns
Algorithms
Data Structures
Refactoring
Simplify


Still disagree with me (after reading this post)?

  • Let's talk (or debate - up to you) - comment below :)
    • What's your opinion on JS having a best function at all?
    • Do you have a function you think beats reduce in JS?
    • Where do you disagree with my thought process?

cleancodestudio image

Did you know I have a newsletter? 📬

If you want to get notified when I publish new blog posts or make major project announcements.

Discussion (28)

Collapse
insidewhy profile image
insidewhy • Edited

For the join case, you can do it much more simply since if you omit the first value, then the first entry in the array is used (reduce is even more powerful than you realise 😛). For some you don't need the ternary or ===, you can just use condition || ....

Collapse
cleancodestudio profile image
Clean Code Studio Author

Hey @insidewhy , thanks for taking the time to drop your coding tips for reduce. Can you show me a quick coding example of what you mean by omitting the first value and automatically using the first entry in the array?

I'm familiar with using || instead of the ternary operator. I usually opt for the ternary operator because it provides better debugging support by default when errors arise.

That first tip you dropped sounds interesting though, I'd love to see an example of what you're specifically referring to!

Collapse
val_baca profile image
Valentin Baca • Edited

You can just do:

['truck', 'car', 'people'].reduce((text, word) => `${text}-${word}`)

> "truck-car-people"
Enter fullscreen mode Exit fullscreen mode

If no initial variable is given (your first example had '') then it just pulls from the input, in this case, 'truck'

You (obviously) need an initial value if the array is empty or you get a very descriptive error:

[3, 2.1, 5, 8].reduce((total, number) => total + number, 0)
> 18.1

[3, 2.1, 5, 8].reduce((total, number) => total + number)
> 18.1

[].reduce((total, number) => total + number, 0)
> 0

[].reduce((total, number) => total + number)
> VM158:1 Uncaught TypeError: Reduce of empty array with no initial value

Enter fullscreen mode Exit fullscreen mode
Collapse
insidewhy profile image
insidewhy • Edited

I usually opt for the ternary operator because it provides better debugging support by default when errors arise.

What debugger are you using? I've never seen this. You always prefer ternary over logical operators because of your debugger? I'm a little scared now. If you're willing to accept code verbosity because of this belief then I hope you're not mistaken.

In situations where one side of the operator is a boolean, I don't see how there can be any ambiguity.

Thread Thread
cleancodestudio profile image
Clean Code Studio Author

Hey insidewhy, I went back and tried to replicate the situation that inspired me to begin using the ternary instead of or.

I wasn't able to replicate it, so I'm thinking it may have been something to do with how ternary/or operators work within the scope of a reactive front-end framework, or how ternary/or operators work on undefined objects (Ex: person.address.street_t - if address doesn't exist), or javascript has simply been updated to correct the issue.

I appreciate you bringing this up in the comments though, I'm headed off for the day - but once I get back on I'm going to try to see if I'm missing something.

If I find something I'll let you know and if I don't then I owe you a big thanks because your boy is about to be using or operators instead of ternaries again :)

Here's to not finding whatever had me spooked when it comes to the or operator

Collapse
siddharthshyniben profile image
Siddharth
Collapse
cleancodestudio profile image
Clean Code Studio Author

A "4 Reasons to avoid using Array.reduce" blog post - nope! Not happening, not while I've got air in my lungs and life in my limbs.

I owe you Siddharth, I'm headed in right now to wage war in the comments section to rise up against the mis-guided non-sense that such an article would have you believe.

Catch you on the flip side, me and my overly opinionated beliefs are about to post up and make an impassioned comment responding to such non-sense.

Reduce is the most powerful and absolutely amazing mother f***** function on the planet, this is my stance, I've chosen my hill - I'm willing to die on it.

Collapse
siddharthshyniben profile image
Siddharth

Not saying that reduce is bad – It's just harder to read. When I was a newbie, it took me a day to understand what it did. That's why I said not to use it. If you care about others being able to read your code, then you should use it less.

Thread Thread
cleancodestudio profile image
Clean Code Studio Author

Okkkay, was that your article post that I commented on @siddharth ? Were you the actual author of the post? If so, I hadn't put two and two together.

I absolutely get where you're coming from from a professional stand point. I'm sharing my highly opinionated view point in a very strong way just to have some fun with it and push reduce forward a bit because I think it gets a bad wrap for readability when my take is that it has less to do with readability and more to do with capability of developers using it.

In a real project you should always take into account your team, and if it is not something that will be ubiquously understood then you should either take the responsibility on to teach it to your team personally or drop it all together.

I figured if we were to start a comment war on each others posts then it would just up kick the conversation and provide both posts with more traffic. Truth be told, I love having the countering perspective directly linked to in this post.

I don't want anyone to think - hey, Zak said it, so it must be true. I try to encourage the challenge of differing perspectives and in cases where it's my personal blog or YouTube channel's brand I'll push the emotion to see what kind of conversations we can spark up.

Collapse
adam_cyclones profile image
Adam Crockett

Clearly math.pow is the most powerful function haha, okay il read it now

Collapse
cleancodestudio profile image
Clean Code Studio Author

Literally lol'd at this one 😁

Collapse
adam_cyclones profile image
Adam Crockett

Exponentially? 😆 Sorry, I'll stop now

Collapse
lukeshiru profile image
LUKESHIRU • Edited

I also wrote about reduce in DEV at one point, and I personally use reduce in some scenarios, but my approach nowadays is to not "default" to it, only use it when is actually necessary. Following your list:

  • 1 and 2. This ones are scenarios which reduce is unbeatable.
  • 3 to 5. Every item that says something like method vs reduce, I generally go with the method designed for that, but I get that you were showing that you can mimic anything with reduce.
  • 6. Grouping People By Names: For this one you can just...
Object.fromEntries([sarah, tim, len].map(person => [person.name, person]))
Enter fullscreen mode Exit fullscreen mode
  • 7. Plucking an array of values by a given key: This is just map:
[sarah, tim, len].map(({ email }) => email);
Enter fullscreen mode Exit fullscreen mode
  • 8. Flattening multiple levels of nested Arrays: You have flat, and flatMap designed for this.
  • 9. Apply Formatters on Strings: Using reduce to create a pipe function I get, but for the actual mapping of the values, map is enough:
const pipe = functions => target => functions.reduce((output, fn) => fn(output), target);
const format = pipe([dashesToSpaces, capitalize, limiter]);

strings.map(format);
Enter fullscreen mode Exit fullscreen mode
  • 10. Group students by rooms: For this case, maybe the following, but reduce is good enough:
const unique = array => [...new Set(array)];
const rooms = unique(students.map(({ room }) => room));

Object.fromEntries(rooms.map(room => [room, students.filter(student => student.room === room)]))
Enter fullscreen mode Exit fullscreen mode

reduce sometimes looks like the only tool you need because it allows you to take an array and turn it into whatever (a new array, an object, a string, a number, you name it), but generally I prefer to use the method that is closer to what I actually want to do. Why use a reduce to turn an array into an object, when I have Object.fromEntries which is made to turn arrays of tuples into objects? Why use reduce to map over some values when I have map for it? And so on.

Still, don't get me wrong, this was a pretty fun article to read. It might be useful for folks wondering what you can do with reduce. And in top of that, I think we can fully agree, there is always a better option than using a for 🤣

Cheers!

Collapse
snickdx profile image
Nicholas Mendez

Indeed JavaScript has enough syntax to implement a solution in multiple ways. However, in writing clean code the solution that is readable and optimal is most preferred. For loops would work fine for most if not all of these use cases.

Relevant Video: youtube.com/watch?v=qaGjS7-qWzg

Collapse
insidewhy profile image
insidewhy

Verbose code is not only boring to write, it can hamper understanding. I think most teams will opt to balance verbosity and ease of understanding. I haven't worked with developers who would favour loops over a functional approach in many years. Although it is necessary when you're writing a framework due to v8's poor optimisation of closures, in other cases performance doesn't matter so much.

Collapse
cleancodestudio profile image
Clean Code Studio Author

I've actually seen this video before. These developers are probably pretty descent developers if they come from Google (I'm at Amazon and will personally tell you that FAANG developers are not always spectacular - they won't be bad, but it's still not common to see spectacular at FAANG. You'll run into great ones more often, but on average, most FAANG developers are simply pretty good and very few are spectacularly good).

The next thought I'd bring up is that for loops in general leave a lot of room for unintended mutations to occur. One of the many benefits of reduce is that it is pure in form (given you don't use an implicit return and intentionally or unintentionally pull in extraneous state that then neglects this point).

One of, if not the, number one cause of JavaScript bugs is unintended mutations.

I do like there idea that the callback should be the second parameter.

As far as readability goes, I'm not onboard with the idea that reduce is less readable. I will say it takes experience using reduce for it to come across as clean as it is to those who are familiar with functional programming.

Once you are familiar with the syntax, I'd say reduce is as readable if not more readable than loops.

const flatArr = []

for (const elem of arr) {
    if (Array.isArray(elem)) 
       flatArr.push(...elem)
    else
       flatArr.push(elem)
}
Enter fullscreen mode Exit fullscreen mode

This example they show, as they pointed out, has a mutation. I agree that within a given modularized block, mutations aren't the most tragic thing in the world.

On the other hand, it is a negative - but it's a trade off for improved readability. If the readability is the same then you don't want to make that trade off.

const flatArr = []

flatArr.reduce((flattened, elem) => [
           ...flattened,  
           ...(Array.isArray(elem) ? elem : [elem])
        ]
, [])
Enter fullscreen mode Exit fullscreen mode

I'd argue by simply implementing a cleaner reduce implementation that we are able to have the best of both worlds.

All we're doing is inversing how we handle the element if it is an array. If the elem is not an array we wrap it in brackets making it an array otherwise we leave it be. Then we call the spread operator knowing that we'll have an array either way.

We don't have to make the trade off of potential mutations for increased readability.

Additionally, we could take this a step further and abstract away the functionality of arrayifying an element if it isn't already an array into it's own one line pure function.

const flatArr = []
const toArray = item => Array.isArray(item) ? item : [item]
flatArr.reduce((flatten, elem) => [...flatten, ...toArray(element)],  [])
Enter fullscreen mode Exit fullscreen mode

Now, using a simple one liner toArray function that is pure, we have removed all of our indentations and made the lines visually appealing by proceeding the length of each next line by more characters.

If you personally ask me which is more readable between

const flatArr = []

for (const elem of arr) {
    if (Array.isArray(elem)) 
       flatArr.push(...elem)
    else
       flatArr.push(elem)
}
Enter fullscreen mode Exit fullscreen mode

and

const flatArr = []
const toArray = item => Array.isArray(item) ? item : [item]

flatArr.reduce((flatten, elem) => [
   ...flatten, ...toArray(element)
],  [])
Enter fullscreen mode Exit fullscreen mode

I'm going with the ladder.

In the ladder example we have variables, functions, parameters, and output.

In the first example we have language keywords like of which is often confused with in or simply not used and instead a longer form implementation is used to get the for loop working properly.

In the first examples we have two paths that tree out, that's an extra dependency - if else.

If this then mutate this variable into this, else mutate this variable into that.

In our second example, we are mapping each element of our array using the toArray function. That's not if this, else that. We are saying this will happen. Next this will happen.

We don't have to worry about accidentally treeing out the wrong direction and unintentionally mutating our variable which would not necessarily throw an error letting us know we messed up.

In our second example, if we don't map the item correctly, we'll immediately get an error - you can't spread a non spreadable thing in JS. If we don't properly map our element into the toArray function, then we don't have an array. Error immediately - we fix the bug, no treeing out, no unintended mutations, and once you spend the time to understand what's going on I would argue it IS much more readable to use reduce than the for loop.

Collapse
cleancodestudio profile image
Clean Code Studio Author • Edited

Oooo, also an after point, this example above is a great show case of why the ternary should be used in place of if else when possible.

If else trees out the possible states. Ternaries do not. This was an interesting one - thanks for your comment @nicholasmenez , it has me thinking deep into the details.

Collapse
csirolli profile image
Christian Sirolli

Now which way of doing each of these things is the best for performance?

Collapse
gweaths profile image
Grant

This was my question all the way through this article. Yes it can do it but which is more performant. That would have been a better comparison or key selling point for me.

Collapse
cleancodestudio profile image
Clean Code Studio Author

You can use big-o-notation to determine the performance of reduce at scale just like with any other traditional looping function.

It's linear time O(n) if you just run through each element in the array one time which is the default behavior of reduce.

Collapse
johnt_f807e8f581 profile image
JohnT

Great way of combining this article with a related YouTube video.

Feels like the video is meant to go right along with this post. Much appreciated!

The screencast does a great job at covering some of the harder to write about topics.

I was lost when you were writing about using parentheses to return an object implicitly returned but it was stupid easy to understand via your youtube video and the follow along coding examples.

This article, I don’t think, talked about using brackets to set object keys names based on variable values.

Your reduce screencast covers it quickly, but through the video example of quickly was enough. Felt concise and to the point. Thanks for this one!!🙏 Really solid stuff!!

Collapse
cleancodestudio profile image
Clean Code Studio Author

Thanks @johnt - glad the Clean Code Studio screencast went hand in hand with this dev to post to help improve the learning process!

Collapse
ozqu profile image
Oskari Ruutiainen

In section 10, third code snippet has an error. Fifth row should have [student] instead of plain student

Collapse
cleancodestudio profile image
Clean Code Studio Author

Thanks @ozqu !

Nice eye, fix made in that section for this post :)

Collapse
val_baca profile image
Valentin Baca

Don't forget the really powerful use: using reduce to implement map :D

Love reduce, it really is the Uberfunction

Collapse
jankapunkt profile image
Jan Küster

This is why I'm on dev.to

Collapse
cleancodestudio profile image
Clean Code Studio Author
Collapse
cleancodestudio profile image
Clean Code Studio Author