Functional Programming Patterns in JavaScript

Functional Programming Patterns in JavaScript

posted Originally published at thefrontendarchitect.com 4 min read

Functional programming (FP) is a paradigm that emphasizes the use of pure functions, immutability, and declarative code. It has become increasingly popular in the JavaScript community due to its ability to produce reliable, maintainable, and scalable code.

This article explores essential functional programming patterns in JavaScript, offering insights into their use cases, benefits, and practical implementations.

Introduction to Functional Programming

Functional programming is built around the idea of composing programs using pure functions and avoiding shared state and side effects. JavaScript, while originally multi-paradigm, offers strong support for functional principles through its first-class functions and built-in methods like map, reduce, and filter.

Adopting functional patterns leads to:

  • Improved predictability and testability
  • Enhanced modularity and reusability
  • Cleaner, more readable code
  • Fewer side effects and state-related bugs

Pure Functions

A function is considered pure if it meets two criteria:

  1. It returns the same output for the same input.
  2. It does not produce side effects such as modifying external state or performing I/O operations.

Example:

// Pure function
const add = (a, b) => a + b;

// Impure function
let count = 0;
const increment = () => ++count;

Pure functions are deterministic, making them easier to test and reason about.

Function Composition

Function composition refers to combining two or more functions to produce a new function. This approach allows complex operations to be built from simpler ones, improving modularity and reusability.

Example:

const addOne = x => x + 1;
const double = x => x * 2;

const addOneThenDouble = x => double(addOne(x));

Generic compose utility:

const compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x);

const process = compose(double, addOne);
process(3); // Returns 8

Function composition encourages declarative programming and simplifies data transformation pipelines.

Currying

Currying is a technique where a function with multiple arguments is transformed into a series of unary functions, each accepting a single argument.

Example:

const multiply = a => b => a * b;

const double = multiply(2);
double(5); // Returns 10

Currying promotes reusability and enables partial application of functions, which is particularly useful when composing higher-order functions.

Higher-Order Functions

Higher-order functions (HOFs) are functions that accept other functions as arguments or return them as results. JavaScript provides built-in HOFs such as map, filter, and reduce.

Examples:

Edit[1, 2, 3].map(x => x * 2);      // [2, 4, 6]
[1, 2, 3].filter(x => x > 1);  // [2, 3]

Custom higher-order function:

const withLogging = fn => (...args) => {
  console.log(`Calling with ${args}`);
  return fn(...args);
};

const add = (a, b) => a + b;
const loggedAdd = withLogging(add);

loggedAdd(3, 4); // Logs input and returns 7

Higher-order functions enable abstraction of control flow and behavior, allowing for clean and reusable code.

Partial Application

Partial application refers to pre-filling a function with some of its arguments, returning a new function that requires the remaining arguments.

Example:

const greet = (greeting, name) => `${greeting}, ${name}!`;

const sayHelloTo = name => greet('Hello', name);
sayHelloTo('Toni'); // "Hello, Toni!"

This technique is useful for creating specialized versions of general-purpose functions.

Recursion

Recursion is a technique where a function calls itself to solve a problem. In functional programming, recursion often replaces loops to maintain immutability.

Example:

const factorial = n => (n <= 1 ? 1 : n * factorial(n - 1));

factorial(5); // Returns 120

Recursion encourages the creation of elegant and expressive solutions, particularly when combined with immutable data structures.

Immutability

Immutability is a core principle of functional programming that involves avoiding mutations of data structures. Instead of modifying existing data, new copies are created with the necessary changes.

Examples:

const arr = [1, 2, 3];
const newArr = [...arr, 4]; // [1, 2, 3, 4]

const obj = { name: 'Toni' };
const updated = { ...obj, age: 35 }; // { name: 'Toni', age: 35 }

Libraries such as Immer and Immutable.js can help enforce immutability at scale.

Declarative Array Methods

Functional programming favors declarative constructs that describe what should happen rather than how it should be done. JavaScript’s array methods support this approach.

Examples:

const numbers = [1, 2, 3, 4];

const squared = numbers.map(n => n ** 2);
const evens = numbers.filter(n => n % 2 === 0);
const sum = numbers.reduce((acc, n) => acc + n, 0);

These methods abstract away the control flow, resulting in concise and readable code.

Point-Free Style

Point-free style, also known as tacit programming, involves writing functions without explicitly referencing their arguments.

Example:

const toUpper = str => str.toUpperCase();
const exclaim = str => str + '!';

const shout = compose(exclaim, toUpper);
shout('hello'); // "HELLO!"

While point-free style can enhance composability, it should be used judiciously to maintain readability.

Libraries and Tools

To facilitate functional programming in JavaScript, several libraries provide utility functions and data structures:

  • Ramda – A utility library designed specifically for functional programming
  • Lodash/fp – A functional wrapper of Lodash with auto-curried and immutable methods
  • RxJS – A library for reactive programming using observables
  • Immutable.js – Provides persistent immutable data structures

These tools can greatly simplify the implementation of functional patterns in large applications.

Practical Example: Data Transformation Pipeline

Consider the task of transforming user data using functional techniques:

const users = [
  { name: 'Toni', age: 30 },
  { name: 'Ana', age: 17 },
  { name: 'Leo', age: 22 },
];

const isAdult = user => user.age >= 18;
const getName = user => user.name;
const toUpper = str => str.toUpperCase();

const adultNamesUpper = users
  .filter(isAdult)
  .map(getName)
  .map(toUpper);

// Result: ['TONI', 'LEO']

This declarative pipeline is concise, expressive, and easy to modify or extend.

Conclusion

Functional programming patterns offer a powerful way to write clean, modular, and maintainable code in JavaScript. By leveraging pure functions, composition, higher-order functions, and immutability, developers can create applications that are more robust and easier to reason about.

Incorporating these patterns into everyday development can lead to significant improvements in code quality and team productivity.

For developers looking to scale their applications or reduce complexity, adopting a functional mindset is a valuable investment.

If you read this far, tweet to the author to show them you care. Tweet a Thanks
0 votes

More Posts

Dao of Functional Programming (Chapter 1: Clean State)

kitfu10 - May 17

How I load javascript?

Aad Pouw - Sep 7

Introduction to Web3.js: The JavaScript Gateway to Ethereum

Toni Naumoski - Jul 13

Starting a new web app with a React project, should you go with React JavaScript or TypeScript?

Sunny - Jun 24

Handling Dynamic Form Controls in Angular: Why `FormRecord` Is a Game Changer

Sunny - Oct 19
chevron_left