Category theory for people who ship JavaScript

A monad is just a monoid in the category of endofunctors.

— Saunders Mac Lane, Categories for the Working Mathematician, 1971

What’s the problem? By slide 13, none. Every word, rebuilt from code you already ship. No burritos required.

The claim

You ship monads every day.

Then flattens nested promises for free. flatMap is named after the monad operation. Only the vocabulary is new.

const data = await fetch(url) .then((r) => r.json()) // Promise<Response> → Promise<Data> .then(validate) // chain stays one level deep .catch(fallback);
then always unwraps one layer — automatically
const names = posts .flatMap((p) => p.tags) // nested arrays flatten .filter((t) => t.active) .map((t) => t.label);
flatMap — literally the monad operation

The plan

Decode the sentence in four moves.

01

Category

Dots and arrows. Composition and identity. Nothing else exists.

02

Endofunctor

A map from types to types that stays home — Array, Promise, Maybe.

03

Monoid

Combine two things, plus a do-nothing value, obeying two laws.

04

Assemble

A monoid object living among functors — that is the monad.

Move 1 · category

A category is just dots, arrows, and two rules.

A B C f g g ∘ f id
const compose = (f, g) => (x) => f(g(x)); // A→B B→C A→C const id = (x) => x;
identity:  id ∘ f = f = f ∘ id
associative:  (h ∘ g) ∘ f = h ∘ (g ∘ f)
we never look inside a dot — that’s the whole trick

Move 2 · functor

A functor lifts arrows — and preserves composition.

A B f F A F B F(f) = map f F
xs.map(double).map(inc) === xs.map((x) => inc(double(x)))
map(f) ∘ map(g) = map(g ∘ f)
read it as a refactor: two passes fuse into one, same result
here F = Array · F(f) = xs.map(f)

Move 2½ · endofunctor

Endo: the functor never leaves the category.

C · the category of types number string boolean User F
number  →  Array<number>
A  →  Promise<A>
A  →  Maybe<A>
objects: types · arrows: functions · F maps both — laws intact, same category

Move 3 · a new category

Now zoom out: whole functors are just dots.

Array Maybe Promise safeHead nat. trans. functor composition Array<Promise<A>>
dots = endofunctors
arrows = natural transformations
safeHead : Array<A> → Maybe<A> — uniformly, no special cases
Breath — the other word you already know

monoid = a way to combine two things, plus a do-nothing value. You’ve passed both to reduce() since your first week.

Move 4 · monoid

Combine, identity, two laws.

1 2 3 4 3 6 = 10
const sum = (xs) => xs.reduce((a, b) => a + b, 0); const concat = (xs) => xs.reduce((a, b) => a + b, "");
associative: (a ⊕ b) ⊕ c = a ⊕ (b ⊕ c)
identity: e ⊕ a = a = a ⊕ e
associativity is why tree-reduction is safe for parallelism

Generalize

No elements? Multiplication becomes a morphism.

M ⊗ M M 1 unit type μ η M ⊗ M ⊗ M M (μ ⊗ id) then μ (id ⊗ μ) then μ both routes agree — that is associativity
⊗ = pairing · 1 = the unit type (both exist in TS)
monoid object = M + μ + η + two laws

Assemble

Swap the set for an endofunctor.

T T A μ · join T A A η · return T A
[[1, 2], [3]].flat(); // μ · join Array.of(1); // η · return p.then(nextPromise); // then auto-joins
carrier set → endofunctor T
the monoid laws, re-expressed = the monad laws

Take it home

The whole dictionary.

Monoid · a setMonad · an endofunctor
carrier set Sendofunctor T — Array, Promise, Maybe
element a ∈ Sa : T A — “a value in a context”
multiply μ(a, b)join : T (T A) → T A
identity eof / return : A → T A
(a ⊕ b) ⊕ c = a ⊕ (b ⊕ c)join ∘ join = join ∘ map join
e ⊕ a = a = a ⊕ ejoin ∘ of = id = join ∘ map of

Why care: the laws are rewrite rules. map(f).map(g) fuses to map(g ∘ f). then-chains re-associate freely. Flatten in any order — same result. That guarantee is what “it’s just a monoid” buys you.

The takeaway

It’s just composition, twice.

Arrows compose in a category. Functors compose as objects. A monad is the monoid you get when that second composition has a unit.

Go deeper: B. Milewski — Category Theory for Programmers (free online) · S. Mac Lane — CWM 1971, §VI.3

1 / 13
→ / click · next step → next slide · R reset · A all · S notes