Introduction
Monads extend functors with flatMap (chain). This allows chaining operations that return containers, avoiding nested containers.
Key Concepts
flatMap/chain: Map then flatten nested container.
Monad Laws: Left identity, right identity, associativity.
Real World Context
Promises are the most common monad in JavaScript—.then() is flatMap. Async/await is syntactic sugar for monadic promise chaining. Optional chaining (a?.b?.c) is a built-in Maybe monad operation. fp-ts, Effect, and neverthrow bring typed monads to TypeScript.
Deep Dive
The Nesting Problem
javascript// Without flatMap Maybe(user) .map(u => Maybe(u.address)) // Maybe(Maybe(address)) .map(m => m.map(a => a.street)); // Nested! // With flatMap Maybe(user) .flatMap(u => Maybe(u.address)) // Maybe(address) .flatMap(a => Maybe(a.street)); // Maybe(street)
Implementing flatMap
javascriptconst Just = x => ({ map: f => Just(f(x)), flatMap: f => f(x), // f returns a Maybe fold: (onNothing, onJust) => onJust(x) }); const Nothing = () => ({ map: f => Nothing(), flatMap: f => Nothing(), // Short-circuit fold: (onNothing, onJust) => onNothing() });
Promise is a Monad
javascript// Promise.then is flatMap fetchUser(id) .then(user => fetchPosts(user.id)) // Returns Promise .then(posts => posts[0]); // Flattened // .then handles both map and flatMap!
Chaining Operations
javascriptconst getStreetName = user => Maybe(user) .flatMap(u => Maybe(u.address)) .flatMap(a => Maybe(a.street)) .flatMap(s => Maybe(s.name)) .fold(() => 'Unknown', name => name);
Common Pitfalls
- Monad confusion — Don't overthink it. If you've used
.then()on a Promise, you've usedflatMap. A monad is just a container withflatMap. - Nesting instead of chaining — Using
.map()when you need.flatMap()creates nested containers:Maybe(Maybe(x))instead ofMaybe(x). - Mixing monad types — You can't directly chain a
Maybeinto anEither. Use natural transformations (converters) between monad types.
Best Practices
- Use
flatMapwhen the mapping function returns a container —flatMap= map + flatten. Iffnreturns a raw value, usemap. If it returns a container, useflatMap. - Think in pipelines —
Maybe(x).flatMap(f).flatMap(g).fold(...)reads like a pipeline where any step can short-circuit. - Start with Promise patterns — If you understand
.then()chaining, you understand monads. Generalize from there.
Summary
Monads have flatMap for chaining container-returning functions. Prevents nesting. Promises are monads. flatMap = map + flatten.
Code Examples
javascript
// The nesting problem: map with container-returning functions
Maybe(user)
.map(u => Maybe(u.address)) // Maybe(Maybe(address)) — nested!
// Solution: flatMap = map + flatten
Maybe(user)
.flatMap(u => Maybe(u.address)) // Maybe(address) — flat!
.flatMap(a => Maybe(a.street)) // Maybe(street)
.fold(() => 'Unknown', s => s);
// Promise.then IS flatMap
fetchUser(id)
.then(user => fetchPosts(user.id)) // Returns Promise — auto-flattened
.then(posts => posts[0]);