Lab 13: Functional Programming
Objective
Apply functional programming (FP) principles in JavaScript — pure functions, immutability, higher-order functions, function composition, currying, and partial application — to write predictable, testable, and elegant code.
Background
Functional programming treats computation as the evaluation of mathematical functions, avoiding shared state and mutable data. JavaScript is a multi-paradigm language that fully supports FP patterns alongside OOP. Modern codebases — React, Redux, Ramda, fp-ts — are built on FP principles. Understanding FP makes you a significantly better JavaScript developer.
Time
45 minutes
Prerequisites
Lab 03 (Functions & Scope)
Lab 04 (Arrays & Objects)
Tools
Node.js 20 LTS
Docker image:
innozverse-js:latest
Lab Instructions
Step 1: Pure Functions & Side Effects
A pure function always returns the same output for the same input and has no side effects.
💡 Pure functions are testable by definition — no mocks, no setup, no teardown. They're also safe to run in parallel, memoize, and compose. The discipline of writing pure functions forces better architecture.
📸 Verified Output:
Step 2: Higher-Order Functions
Functions that take or return other functions. The foundation of FP in JavaScript.
💡 Memoization is a classic FP optimization: cache results of pure functions keyed by arguments. It's safe only for pure functions — impure functions with side effects would return stale cached results.
📸 Verified Output:
Step 3: Function Composition
Combine small, focused functions into larger ones.
💡
pipevscompose:pipe(f, g, h)(x)=h(g(f(x)))— left to right, readable as a sequence of transformations.compose(f, g, h)(x)=f(g(h(x)))— right to left, mathematical notation. Preferpipefor readability.
📸 Verified Output:
Step 4: Currying & Partial Application
Currying transforms f(a, b, c) into f(a)(b)(c). Partial application pre-fills some arguments.
💡 Currying enables point-free style — you build specialized functions by partial application rather than anonymous functions.
prices.map(tenPercentOff)reads like English. Compare toprices.map(p => discount(0.1, p))— same result, less expressive.
📸 Verified Output:
Step 5: Immutability — Working Without Mutation
Avoid mutations to prevent bugs in shared state.
💡 Spread for nested objects is verbose but explicit. Libraries like Immer let you write "mutating" code that's actually immutable under the hood — they use Proxy to intercept mutations and produce new objects. For complex state, consider Immer.
📸 Verified Output:
Step 6: Functors & Monads — the Maybe Pattern
A practical introduction to monadic patterns for safe null handling.
💡 The Maybe monad eliminates
nullreference errors by wrapping values in a container.mapapplies a function only if the value exists, skipping it onnull/undefined. This is the pattern behind Optional in Java, Option in Rust/Scala, and JavaScript's optional chaining?..
📸 Verified Output:
Step 7: Transducers — Efficient Data Pipeline
Compose transformations that iterate only once, no matter how many steps.
💡 Transducers iterate the source once regardless of pipeline length. With large datasets (millions of rows), they avoid creating N intermediate arrays. Libraries like Ramda and transducers-js bring this to production code.
📸 Verified Output:
Step 8: Putting It Together — Functional Data Processing Pipeline
Build a complete FP-style data analysis pipeline.
💡 This pipeline is entirely composable. Each utility (
filterBy,groupBy,sumBy) can be reused in any combination. Adding a new analysis is one morepipe()call. This is the power of FP: small pieces, infinite combinations.
📸 Verified Output:
Verification
Expected: Tech spending and user breakdown printed correctly.
Common Mistakes
Mutating array arguments
Use [...arr], .slice(), or .map() to copy first
Treating impure HOFs as pure
Math.random(), Date.now() inside HOFs = impure
Deep mutation in spread
Spread is shallow — nested objects still share references
Over-engineering with FP
Use FP where it adds clarity; OOP is fine for stateful entities
Forgetting curried function arity
curry uses .length — rest params ...args have length 0
Summary
You've applied pure functions, higher-order functions, pipe/compose, currying, partial application, immutability, the Maybe monad, transducers, and a full FP data pipeline. Functional programming isn't about avoiding all state — it's about being deliberate: isolate side effects, prefer pure functions, and compose small pieces into powerful wholes.
Further Reading
Ramda.js — FP utility library for JavaScript
Last updated
