Immutability and Pure Functions
Introduction: Why Immutability Matters
In our last lesson, we learned how to use higher-order functions like filter and map to process collections of data. You might remember that we briefly used the syntax [...transactions] before sorting a list. We did this because some JavaScript methods change the original data they are given. In functional programming, we try to avoid this behavior entirely. This concept is called immutability.
Immutability means that once a piece of data is created, it should never be changed. Instead of modifying an existing object or array, we create a brand-new copy that contains the changes we need. This approach is very important when building reliable systems, such as a banking application. If you have a record of a transaction, you want to be sure that no part of your code can accidentally change its amount or ID later on. By the end of this lesson, you will know how to update objects and lists safely using pure patterns.
The Danger Of Mutation
When we change an object directly, we call it mutation. This can lead to very confusing bugs because JavaScript passes object references by value. That means two variables can hold references to the same object, so mutating through one reference is visible through the other.
Imagine a scenario where one part of your app is calculating a total balance while another part is marking a transaction as "completed." If the second part of the code accidentally changes the amount of the transaction while updating its status, your balance calculation will suddenly be wrong. These types of "side effects" make code very difficult to test and debug because the data can change at any time from anywhere in your program. To prevent this, we treat our data as if it were carved in stone.
Pure Object Updates With The Spread Operator
To update an object without changing the original, we use the spread operator, which looks like three dots .... This operator allows us to "spread" the properties of an existing object into a new one. We can then list any properties we want to change after the spread operator, and those new values will overwrite the old ones in the new object.
In the markCompleted function, we take a transaction object called tx. Instead of saying tx.status = "completed", we return a new object wrapped in parentheses. Inside this new object, we spread the properties of tx and then specify that the status should be "completed." When we run this code, the output shows that the original transaction remains "pending," while our new version is "completed."
Updating Arrays Without Mutation
Updating a specific item inside an array without changing the original array is a bit more complex than updating an object. We cannot simply change an index like arr[1] = newValue because that would mutate the original list. Instead, we can create a helper function that uses the slice method and the spread operator to build a fresh array.
The replaceAt function works by taking three parts and joining them into a new array. First, it uses slice(0, i) to get all the items before the one we want to change. Second, it places our new value in the middle. Third, it uses slice(i + 1) to get all the items that come after the one we replaced. By spreading these pieces into a new array literal [...], we get a perfect copy with exactly one change.
In our example, the second transaction in the next array has an amount of 999, but the original transactions list still shows the original amount of 50.
Defining Pure Functions
Now that we know how to handle data safely, we can talk about the concept of a pure function. A function is considered pure if it follows two strict rules. First, it must always return the same output if you give it the same input. Second, it must have no side effects, meaning it doesn't change any variables outside of itself or modify its arguments.
The total function is a pure function. It takes a list of transactions and uses reduce to calculate the sum of their amounts. It doesn't modify the transactions array, and it doesn't rely on any hidden data that might change later. Because it is pure, we can call it a hundred times with the same list, and we will always get the same result. This predictability makes pure functions very easy to test and move around in your project without breaking things.
Summary And Practice Preparation
In this lesson, we explored the core principles of immutability and pure functions. We learned that modifying data directly can lead to unpredictable bugs, and we discovered how to use the spread operator to create updated copies of objects. We also built a replaceAt helper to safely update items within an array using the slice method. Finally, we defined the rules for pure functions and saw how they provide a reliable foundation for our code.
These techniques allow you to write programs where data flows clearly and safely from one step to the next. In the following exercises, you will practice transforming transaction data using these immutable patterns. Focus on ensuring that your original data remains untouched as you build your solutions. Good luck!
