- Published on
Functional Programming
Functional programming in JavaScript emphasizes immutability, utilizing pure functions, and leveraging higher-order functions like map, filter, and reduce for concise and declarative code.
Table of Contents
Immutability
Immutability refers to the principle of not changing the state of data once it's created. In JavaScript, this means not modifying objects or arrays directly but instead creating new ones with the desired changes.
Example:
// Mutable approach (not recommended)
let mutableArray = [1, 2, 3]
mutableArray.push(4) // Modifies original array
console.log(mutableArray) // Output: [1, 2, 3, 4]
// Immutable approach (recommended)
let immutableArray = [1, 2, 3]
let newArray = [...immutableArray, 4] // Create a new array with the desired change
console.log(newArray) // Output: [1, 2, 3, 4]
console.log(immutableArray) // Output: [1, 2, 3] (original array remains unchanged)
Pure Functions
Pure functions are functions that, given the same input, will always return the same output and have no side effects. They don't modify variables outside their scope or rely on external state.
Example:
// Impure function (not pure)
let counter = 0
function impureAdd(x) {
counter++ // Modifies external state
return x + counter
}
console.log(impureAdd(5)) // Output: 6
console.log(impureAdd(5)) // Output: 7 (side effect: counter incremented)
// Pure function
function pureAdd(x, y) {
return x + y // No side effects, always returns the same result for the same inputs
}
console.log(pureAdd(5, 3)) // Output: 8
console.log(pureAdd(5, 3)) // Output: 8 (no side effects)
Map, Filter, Reduce
These are higher-order functions commonly used in functional programming to operate on arrays and produce a result without mutating the original array.
Map
: Transforms each element of anarray into another value
using a provided function.Filter
: Creates anew array
with all elements that pass the test implemented by the provided function.Reduce
: Reduces an array to asingle value
by applying a function to each element and accumulating the result.
Example:
// Map
const numbers = [1, 2, 3, 4]
const doubled = numbers.map((num) => num * 2)
console.log(doubled) // Output: [2, 4, 6, 8]
// Filter
const evenNumbers = numbers.filter((num) => num % 2 === 0)
console.log(evenNumbers) // Output: [2, 4]
// Reduce
const sum = numbers.reduce((acc, curr) => acc + curr, 0)
console.log(sum) // Output: 10 (1 + 2 + 3 + 4)
Reduce
reduce
is a higher-order function typically employed to transform
a list of values into a single value
. It iterates through each element of the list, applies a specified function to pair them together, and accumulates the results.
const array = [1, 2, 3, 4, 5]
// Using reduce to sum up the array elements
const sum = array.reduce((accumulator, currentValue) => accumulator + currentValue, 0)
console.log(sum) // Output: 15
array.reduce()
takes two arguments: acallback function
and an optionalinitial value
for theaccumulator
.- The callback function passed to
reduce
takesfour arguments
:accumulator
,currentValue
,currentIndex
(optional), and thearray itself
(optional). However, in most cases, you'll only need the first two arguments:accumulator
andcurrentValue
. - The
accumulator
parameteraccumulates
thereturn values
of the callback function. It starts with theinitial value
provided (if any), or with thefirst element
of thearray
if no initial value is provided. - The
currentValue
parameter represents thecurrent
element being processed in the array.
Here's a breakdown of the example:
accumulator
starts with an initial value of 0.- For each
element
of the array, thecallback
function adds thecurrent
element (currentValue) to theaccumulator
(accumulator). - After iterating through all elements,
reduce
returns thefinal
accumulated value.
// Flattening an Array of Arrays
const arrays = [
[1, 2],
[3, 4],
[5, 6],
]
const flattenedArray = arrays.reduce((accumulator, currentValue) => {
return accumulator.concat(currentValue)
}, [])
console.log(flattenedArray) // Output: [1, 2, 3, 4, 5, 6]
// Counting occurrences of elements in an array
const words = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple']
const wordCount = words.reduce((acc, word) => {
acc[word] = (acc[word] || 0) + 1
return acc
}, {})
console.log(wordCount)
// Output: { apple: 3, banana: 2, orange: 1 }
// Here `acc` is an `object` where each key represents a `unique` word,
// and the value represents the `count` of occurrences of that word.
// If the word doesn't exist in the accumulator yet, it initializes its count to 0 before incrementing.
// Transforming an array of objects
const people = [
{ name: 'Kumar', age: 30 },
{ name: 'Rajnish', age: 25 },
{ name: 'Rahul', age: 35 },
]
const peopleDetails = people.reduce(
(acc, person) => {
acc.names.push(person.name)
acc.totalAge += person.age
return acc
},
{ names: [], totalAge: 0 }
)
console.log(peopleDetails)
// Output: { names: ['Kumar', 'Rajnish', 'Rahul'], totalAge: 90 }
// Here reduce is utilized to transform an array of objects
// containing name and age properties into a single object
// containing an array of names (names) and the total age (totalAge) of all people.
// The initial value of the accumulator is an object with empty names array and totalAge set to 0.
Question
:
Given an array of integers nums
and an integer target
, return indices of the two numbers such that they add up to target
.
You may assume that each input would have exactly one solution, and you may not use the same element twice.
You can return the answer in any order.
const twoSum = function (nums, target) {
const map = {}
return nums.reduce((result, num, index) => {
const complement = target - num
if (map.hasOwnProperty(complement)) {
result.push(map[complement], index)
}
map[num] = index
return result
}, [])
}
Here's a breakdown of the example:
-
result
: This is theaccumulator
variable. In this context, it's an array where the indices of the two numbersadding
up to thetarget
will be stored.Initially
, it's anempty
array
. -
num
: This represents thecurrent element
of the numsarray
being processed during each iteration of thereduce
method. -
index
: This represents theindex
of thecurrent element
(num) within the nums array. -
The
fourth argument
is not explicitly used in thecallback function
. In JavaScript,reduce
provides thecurrent array
being processed as thefourth
argument to the callback function, but it's not utilized in this specific example.
Here's how the code works:
- For each
element
(num) in thenums
array, it calculates thecomplement
, which is thedifference
between thetarget
and thecurrent
num. - It then
checks
ifmap
(an object) has aproperty
with thekey
equal to thecomplement
. If it does, it means that thecurrent
num plus thecomplement
(which is already seen before) equals thetarget
. In this case, itadds
theindices
of thecomplement
and thecurrent
num to theresult
array. - It then
stores
thecurrent
num and itsindex
in themap
object. - Finally, it
returns
theresult
array, whichcontains
theindices
of thetwo numbers
that add up to thetarget
. If no such pair is found, it returns anempty array
[].
/**
* @param {number[]} prices
* @return {number}
*/
var maxProfit = function (prices) {
return prices.reduce(
(maxProfit, currentPrice) => {
if (currentPrice < maxProfit.minPrice) {
maxProfit.minPrice = currentPrice
} else if (currentPrice - maxProfit.minPrice > maxProfit.maxProfit) {
maxProfit.maxProfit = currentPrice - maxProfit.minPrice
}
return maxProfit
},
{ minPrice: Infinity, maxProfit: 0 }
).maxProfit
}
Explanation:
- We use the
reduce()
function on theprices
array with an initialaccumulator
object containingminPrice
initialized toInfinity
andmaxProfit
initialized to 0. - For each element in the
prices
array, we compare it with theminPrice
in theaccumulator
. If it'sless
thanminPrice
, we updateminPrice
to thecurrent
price. - If the
difference
between thecurrent
price andminPrice
isgreater
than themaxProfit
in theaccumulator
, we updatemaxProfit
accordingly. - Finally, we return the
maxProfit
from theaccumulator
.
function productExceptSelf(nums) {
const totalProduct = nums.reduce((acc, num) => acc * num, 1)
return nums.map((num) => totalProduct / num)
}
// Example usage
const nums = [1, 2, 3, 4]
console.log(productExceptSelf(nums)) // Output: [24, 12, 8, 6]
Explanation:
- We first calculate the
total
product of allelements
in thenums
array usingreduce()
. - Then, for each
element
in thenums
array, wedivide
thetotal
product by thecurrent
element to get theproduct
of allelements
except thecurrent
element. - Finally, we return the
resulting
array.
Why Mutable approach (not recommended)
In JavaScript, mutability
can lead to unintended
side effects, especially in complex applications or when working in a team environment. Here's why mutating data directly is often discouraged:
Unintended Side Effects
:Mutating
data directly can lead to unexpected behavior, especially when dealing with sharedstate or asynchronous code
. It can introduce bugs that are hard to track down and debug.Functional Programming Paradigm
: Functional programming promotesimmutability
as a core principle. Embracingimmutability
leads to more predictable and easier-to-understand code, which aligns well with the functional programming paradigm.Debugging and Testing
:Immutable
data makes debugging and testingeasier
since you can trust thatdata won't change unexpectedly
. Testing pure functions and immutable data structures tends to be simpler and more reliable.
While mutability
may offer performance
benefits in some cases, modern JavaScript engines are optimized
to handle immutable
data efficiently, and the performance trade-off is often negligible
compared to the benefits gained in code maintainability
and reliability
.