30 Days of JavaScript: Introduction to Functional Programming, Day 18

30 Days of JavaScript: Building a Simple Weather App
30 Days of JavaScript: Introduction to Functional Programming, Day 18

Explore Functional Programming in JavaScript. Learn key concepts like pure functions, immutability, and higher-order functions to write cleaner, more maintainable code.

Day 18. You’ve written a fair bit of JavaScript by now, and some of it has probably bitten you: a function that quietly changed a variable somewhere else, or a bug you couldn’t reproduce because the same input kept giving different answers. Functional programming is the discipline that makes that whole class of bug rare.

It’s less a new syntax than a way of thinking about code. Keep your functions predictable, keep your data from mutating out from under you, and programs get easier to test, debug, and reason about. That’s the pitch. Today we’ll walk the pieces that actually matter in practice: pure functions, immutability, higher-order functions, and currying. Each one is small on its own. Together they change how you structure everything.

Table of Contents

Understanding Functional Programming

Functional programming treats your program as a set of functions that take input and return output, and not much else. No reaching out to mutate shared state, no side effects buried in the middle of a calculation. When a function’s only job is to turn its arguments into a return value, you can trust it. That trust is what makes the code easier to debug, test, and maintain.

Key Concepts:
  • Immutable Data: once a value is created, you don’t change it. Updates produce new data instead.
  • Pure Functions: same input, same output, every time, with no side effects.
  • Declarative Programming: you describe what you want, not the step-by-step how.

Pure Functions and Immutability

These two ideas do most of the work in functional programming. Let’s take them one at a time.

Pure Functions

A pure function gives you the same output for the same input, every time, and it doesn’t touch anything outside itself. No writing to global state, no mutating the arguments it was handed. Call it a thousand times and the world looks exactly the same after as it did before.

JS
/**
 * Adds two numbers and returns the result.
 *
 * @param {number} a - The first number.
 * @param {number} b - The second number.
 * @returns {number} - The sum of a and b.
 */
function sum(a, b) {
  return a + b;
}
console.log(sum(3, 4)); // Output: 7

That’s about as pure as it gets. Nothing outside those two parameters affects the result, and nothing outside the function changes because you called it.

Immutability

Immutability means once a value exists, you don’t modify it in place. Need a change? You build a new value and leave the original alone. In JavaScript, Object.freeze is the built-in way to lock an object or array.

JS
/**
 * Demonstrates immutability using Object.freeze.
 *
 * @returns {void}
 */
const array = Object.freeze([1, 2, 3]);
array[0] = 4; // TypeError: Cannot assign to read-only property

Two honest caveats here. That TypeError only fires in strict mode. In sloppy (non-strict) mode the assignment fails silently, no error at all, which is arguably worse because the bug hides. ES modules run in strict mode by default, so most modern code is covered, but it’s worth knowing. And Object.freeze is shallow: it locks the top level only. Objects and arrays nested inside a frozen object are still fair game unless you freeze them too.

Higher-Order Functions and Currying

Once functions are just values, you can pass them around like any other value. That opens up two techniques worth having in hand.

Higher-Order Functions

A higher-order function either takes a function as an argument, returns a function, or both. That’s the whole definition. map, filter, and reduce are all higher-order functions, which is why they show up everywhere in functional code.

JS
/**
 * Processes a person's name using a greeting function.
 *
 * @param {Function} greetFunction - The function to process the greeting.
 * @param {string} name - The name to be greeted.
 * @returns {string} - The greeting message.
 */
function greeting(name) {
  return 'Hello, ' + name + '!';
}
function processPerson(greetFunction, name) {
  return greetFunction(name);
}
console.log(processPerson(greeting, 'John')); // Output: Hello, John!
Currying

Currying takes a function that wants several arguments and turns it into a chain of functions that each take one. It sounds academic, but it’s how you build a specialized function out of a general one.

JS
/**
 * Multiplies two numbers using currying.
 *
 * @param {number} a - The first number.
 * @returns {Function} - A function that takes a second number and returns the product.
 */
function multiply(a) {
  return b => a * b;
}
const multiplyByTwo = multiply(2);
console.log(multiplyByTwo(3)); // Output: 6

multiplyByTwo is multiply with its first argument baked in. You’ve made a reusable tool without repeating yourself.

Real-World Functional Programming Patterns

This isn’t only theory. The moment you work with arrays, functional programming shows up. Common array methods like map, filter, and reduce let you transform data without a single manual loop or a mutated counter.

Map, Filter, and Reduce

Chain them and you can describe a whole transformation as a pipeline: map to change each item, filter to keep the ones you want, reduce to fold everything down to a single value.

JS
/**
 * Uses map, filter, and reduce to process an array of numbers.
 *
 * @returns {number} - The sum of the doubled even numbers.
 */
const numbers = [1, 2, 3, 4];
const doubled = numbers.map(n => n * 2);
const even = doubled.filter(n => n % 2 === 0);
const sum = even.reduce((total, n) => total + n, 0);
console.log(sum); // Output: 20

Read it top to bottom: double every number to get [2, 4, 6, 8], keep the even ones (all of them this time), then add them up. Two plus four plus six plus eight is 20. No loop, no running total you have to manage by hand.

Memoization

Memoization caches the result of an expensive call, so the next time you ask for the same input you get the stored answer back instead of recomputing it.

JS
/**
 * Memoized Fibonacci function.
 *
 * @param {number} n - The position in the Fibonacci sequence.
 * @param {Object} memo - An object to store previously calculated results.
 * @returns {number} - The Fibonacci number at position n.
 */
function fibonacci(n, memo = {}) {
  if (memo[n]) return memo[n];
  if (n <= 1) return n;
  memo[n] = fibonacci(n - 1, memo) + fibonacci(n - 2, memo);
  return memo[n];
}
console.log(fibonacci(10)); // Output: 55

Naive recursive Fibonacci recomputes the same values over and over. The memo object remembers what it already worked out, which turns an exponential pile of calls into a manageable one.

Real-World Applications

A couple more patterns you’ll reach for once these ideas click.

Function Composition

Composition wires functions together so the output of one feeds straight into the next. Instead of nesting calls by hand, you build one function out of several small ones.

JS
/**
 * Composes two functions, applying them in sequence.
 *
 * @param {Function} f - The first function.
 * @param {Function} g - The second function.
 * @returns {Function} - A composed function.
 */
function compose(f, g) {
  return x => f(g(x));
}
function double(x) {
  return x * 2;
}
function increment(x) {
  return x + 1;
}
const doubleThenIncrement = compose(increment, double);
console.log(doubleThenIncrement(5)); // Output: 11

compose(increment, double) runs double first, then increment: five doubles to ten, then increments to 11. Small pieces, snapped together.

Immutable Data Structures

When you want immutability guaranteed across a whole structure, a library like Immutable.js handles it for you. Its collections never mutate; every operation hands back a new one.

JS
/**
 * Demonstrates the use of Immutable.js for immutable data structures.
 *
 * @returns {void}
 */
const { List } = require('immutable');
const numbers = List([1, 2, 3]);
const newNumbers = numbers.push(4);
console.log(numbers === newNumbers); // Output: false

push doesn’t change the original list. It returns a brand-new one, which is why the two aren’t equal. Worth a caveat, though: Immutable.js is a real dependency to carry. For smaller cases, spreading into a new array or object gets you most of the way there without it.

Conclusion

That’s functional programming in one sitting: pure functions, immutability, higher-order functions, currying, and the array methods and patterns that put them to work in real code.

You don’t have to rewrite everything in this style tomorrow. Reach for it where it pays. Pure functions for logic you want to test, immutable updates where shared state keeps burning you, map, filter, and reduce instead of hand-rolled loops. Small, boring wins that add up to code you can trust.

What’s next?

Day 19 gets into JavaScript animation basics: how to move elements around and make an interface feel alive using nothing but JavaScript. See you there.

Next: 30 Days of JavaScript: JavaScript Animation Basics, Day 19

Leave a Comment

Your email address will not be published. Required fields are marked *


Scroll to Top