Study notes · full-stack edition

Functional
Programming, demystified.

A paradigm built on one stubborn idea: build programs by composing functions that transform data — and change nothing else.

// the whole thing in one line
OOP organizes code around objects that hold and change state.
FP organizes it around functions that transform data without changing anything.
input data f() pure g() pure h() pure output data'

// 01 · the core idea

What it actually is

Functional programming treats computation as evaluating functions that transform data, instead of running a sequence of statements that change things in memory. You feed data in, get new data out, and nothing on the outside is touched.

That's the entire philosophy. Everything else — purity, immutability, composition — is just the discipline that keeps that promise true.

The good news: you already write half of this. React's pure components, immutable state updates, and useEffect quarantining side effects are all FP ideas wearing a UI costume. This guide is about getting deliberate with patterns you half-know.

// 02 · vs. how you write today

How it differs from your stack

Most of what you ship is imperative (step-by-step statements that mutate state) or OOP (objects bundling data with methods that change their own internals). FP refuses both moves.

AxisImperative / OOPFunctional
statemutated in place, often sharedimmutable — new values, originals untouched
side effectsscattered throughout logicpushed to the edges, core stays pure
building blocksloops, statements, objectsfunctions composed together
same input →maybe different output (hidden state)always the same output
testingmock the DB, the request, the worldpass data, assert on data

// 03 · see it · tap to compare

Pure vs. impure, live

Same cart-total job, two ways. Watch what leaves the function's own body.

reaches outside// leans on shared state + does IO mid-calculation
let total = 0;
function addToCart(item) {
  total += item.price;   // mutates outer state
  db.save(item);        // side effect, hidden inside
  log(`added ${item.name}`);
  return total;         // depends on call history
}
Called twice with the same item, this returns different numbers and quietly writes to the database. Good luck testing it.

// 04 · the four pillars

What you actually practice

Eighty percent of the day-to-day value lives here. No new language required.

01 — purity

Pure functions

Same input, same output, no reach outside the function. Separate the calculation from the effect.

02 — immutability

Don't mutate

Return new values; leave originals alone. You already do this so React re-renders correctly.

03 — first-class fns

Functions as data

Pass them, return them, store them. arr.map(fn) is this — FP just makes it the primary tool.

04 — composition

Compose small parts

Build behavior by piping small functions together instead of one big procedural block.

immutability// mutate nothing — produce a new array
const xs = [1, 2, 3];
const ys = [...xs, 4];   // xs is still [1,2,3]
compositionconst pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);

const cleanText = pipe(normalizeWhitespace, trim, capitalize);
cleanText(raw);   // data → f → g → h, reads like a recipe

// 05 · the words that sound scarier than they are

Jargon, deflated

Currying

Bake in the stable argument, get back a specialized function. You've met it in Redux middleware: store => next => action.

Higher-order function

A function that takes or returns a function. map, filter, every HOC. You do this hourly.

Functor / Monad

A much dumber idea than the internet claims. Promise is basically a monad; Array.map is functor behavior. It's just a pattern for chaining computation that carries context.

Option / Result

The one fancy idea that pays rent. Make "might be missing" or "might fail" explicit in the type so the compiler forces you to handle it.

Result — no more magic -1type Result<T> = { ok: true; value: T }
                | { ok: false; error: string };

function parseScore(raw: unknown): Result<number> {
  if (typeof raw !== "number") return { ok: false, error: "not a number" };
  return { ok: true, value: Math.round(raw * 100) };
}
// the caller literally cannot forget the failure case

// 06 · why anyone bothers

Why FP exists

Predictability

Pure functions can't surprise you — no hidden state, no "what changed this?" Easy to reason about, trivial to test.

Managing complexity

Small composable pieces + effects quarantined at the edges keep big systems from becoming a tangle of who-mutated-what.

Safe concurrency

Nothing shared and mutable means nothing to race over. Parallel code stops being a minefield.

// 07 · is it used in production?

Where it actually lives

Yes — heavily, though rarely as "100% pure FP." It shows up two ways: whole languages in specific industries, and functional style inside mainstream stacks.

As whole languages

The pattern is telling: finance, telecom, data pipelines, concurrency-heavy backends — anywhere "predictable, no hidden state, safe to parallelize" is worth real money. Haskell (Meta's spam filter, Standard Chartered), Scala (Spark, streaming/data eng), Erlang & Elixir (WhatsApp, Discord's real-time backend), Clojure (Nubank's fintech core), OCaml (Jane Street trading), F# (quant/.NET shops).

As style, inside normal stacks — where you already live

React's whole modern paradigm is FP applied to UI. Redux reducers are literally pure functions over immutable state. RxJS is functional composition over event streams. map/filter/reduce pipelines run every data job. And fp-ts / Effect are gaining ground in backend TypeScript.

// 08 · the languages · verdict for your path

Which language, and should you?

Elmfrontend · pure
Friendliest on-ramp, clicks with your React brain. A pure language won't let you cheat, so the concepts finally stick. Build one small app, then come back to TS.
learn · once
F# / OCamlpragmatic · .NET
Functional but close to real work, less dogmatic. OCaml runs Jane Street; F# suits finance and .NET shops.
optional
Clojurelisp · JVM
Immutability by default with a Lisp flavor. Nubank, Walmart, CircleCI run it. Worth it only if the parentheses spark joy.
optional
Elixir / Scalabackend · scale
Elixir for high-concurrency real-time systems; Scala for data engineering. Great to know if the job calls for it — not as a learning vehicle.
job-driven
Haskellthe deep end
Learn it and everything else feels shallow — but the ramp is steep and the direct job ROI for full-stack work is near zero. A hobby, not a career move.
skip · unless fun

// 09 · the roadmap · get better, don't rabbit-hole

How to actually learn it

Phase 1 · ~2–3 wks · in TS/Python

Rewire your defaults

Write pure functions by habit. Stop mutating. Small composable functions. Push side effects to the edges. Win condition: refactor one Django view so the business logic is pure and testable with zero Django imports.

Phase 2 · ~2–4 wks · still TS

The sharpening concepts

Composition & pipelines, currying/partial application, closures as a design tool, and Option/Result for null-safety. Understand functors/monads only to "Promise already does this" — then stop.

Phase 3 · ~3–4 wks · optional, once

Feel it in a pure language

Build one small Elm app. Purpose is learning, not career. A pure language forces the concepts to stick, then you return to TS a better engineer.

Hard stop · career dead-ends

Where to quit

Monad transformers, free monads, tagless-final, category theory as study, going all-in on fp-ts in a team codebase. Intellectually fun, professionally irrelevant for full-stack. Everything past here is a hobby.

// the honest takeaway

You won't get hired to write Haskell.

But functional style is now table stakes in frontend and increasingly in backend TS. The real ROI isn't mastering a paradigm — it's that Phases 1–2 make you visibly better at the React / Redux / TS / Django work you're already paid for: cleaner, more testable, fewer "why did this break" bugs. Learn the ideas liberally. Use the heavy machinery sparingly. Ship.

// end of notes · pure in the middle, effects at the edges