CONCEPT 08 · ABSTRACTIONS

The Monad Pattern

"Chain operations where each step might fail or be async."

monadflatmapchaining
Plain English

A monad is a pattern for chaining operations where each step might change the "context" — it might fail, be asynchronous, produce multiple results, etc. The key behavior: if any step in the chain fails or produces None, the remaining steps are skipped automatically. You write each step independently; the pattern handles the routing.

Analogy

A conveyor belt with an automatic ejection system. Each station does one job. If any station detects a problem, the item is ejected and bypasses all remaining stations. You write each station's logic independently — you don't add "if previous station failed, skip me" to every station. The belt handles that.

You already know this
JavaScript Promises: .then() is flatMap. Rejection short-circuits the chain to .catch()async/await: just syntactic sugar for Promise chaining — same monad, nicer syntaxOptional chaining: user?.address?.city — returns undefined on first null, skips the restArray.flatMap(): maps and flattens — flatMap is the core monadic operationGo error handling pattern: if err != nil { return err } repeated — this is what monads automate
Interactive Demo
Option/Maybe chain — click any step to return None
This is how Promises, async/await, and optional chaining work under the hood. One failure = the rest skip automatically.
getUser(42)
Some(User{id:42, name:"Alice"})
getOrders(user)
Some([Order#1, Order#2])
calcTotal(orders)
Some(Total: $148.00)
Final: SomeTotal: $148.00
Code Example
JavaScript
// WITHOUT monadic chaining — manual null/error checks at every step
async function getUserInvoice(userId) {
  const user = await fetchUser(userId);
  if (!user) return null;  // manual check

  const orders = await fetchOrders(user.id);
  if (!orders || orders.length === 0) return null;  // manual check

  const invoice = await generateInvoice(orders);
  if (!invoice) return null;  // manual check

  return invoice;
}

// WITH monadic chaining (Promise IS a monad)
// .then() only runs if the previous step succeeded
// .catch() handles ANY failure in the entire chain
async function getUserInvoice(userId) {
  return fetchUser(userId)
    .then(user => fetchOrders(user.id))      // skipped if user null
    .then(orders => generateInvoice(orders)) // skipped if orders null
    .catch(err => ({ error: err.message })); // handles any step's failure
}

// Result/Option monad — same pattern, explicit types
function findUser(id)    { return id > 0 ? Some(users[id]) : None; }
function findAddress(u)  { return u.addressId ? Some(addrs[u.addressId]) : None; }
function formatCity(a)   { return Some(a.city.toUpperCase()); }

// flatMap chains — None from any step skips the rest
const city = findUser(42)
  .flatMap(user    => findAddress(user))
  .flatMap(address => formatCity(address));
// city is Some("NEW YORK") or None — never a crash
Apply when
Chaining operations that can fail: fetchUser → fetchProfile → fetchPosts — each step can fail
Sequences of async operations where each step depends on the previous
Multi-step validation where you want to stop at the first error (fail-fast)
Database transactions: a series of writes where any failure should abort the whole sequence
Any time you write "if (result != null && result.success) { ... next step ... }" — that's a monad waiting to happen
Check Your Understanding
You have: fetchUser(id).then(u => fetchProfile(u.id)).then(p => fetchPosts(p.blogId)). What happens if fetchUser resolves with null (user not found)?