CONCEPT 09 · ABSTRACTIONS

Applicative vs Monad

"Independent things should run independently."

applicativevalidationparallel
Plain English

Monads chain sequential steps where each step may depend on the previous result. Applicatives combine independent operations — operations that don't depend on each other's results. The key difference: Applicative can run things in parallel and collect ALL results (including ALL errors); Monad runs things sequentially and short-circuits on first failure.

Analogy

Making breakfast. Toasting bread and boiling eggs are independent — you do both simultaneously (Applicative). But frying an egg in the toast drippings requires the toast to finish first — that's a dependency (Monad). Using a Monad when operations are independent wastes time. Using Applicative when there are real dependencies produces incorrect results.

You already know this
Promise.all([fetchUser, fetchSettings, fetchPermissions]) — independent, run in parallel, collect all results. That's Applicative.fetchUser().then(user => fetchOrdersFor(user.id)) — sequential, result depends on previous. That's Monad.Form validation: checking all fields independently, showing all errors at once — Applicative.Multi-API fan-out: calling 3 independent services and combining their results — Applicative.
Interactive Demo
Form validation — Monad vs Applicative
Click validations to make them fail. Switch modes to see how each strategy handles multiple failures.
Email format valid
✓ Valid(click to toggle)
Password length ≥ 8
✓ Valid(click to toggle)
Username available
✓ Valid(click to toggle)
✓ All validations passed!
Monad (Either/flatMap): user sees only the first error — frustrating UX.
Code Example
JavaScript
// MONAD validation — sequential, fail-fast, stops at first error
function validateUserMonad(data) {
  const name = validateName(data.name);
  if (!name.ok) return { ok: false, errors: [name.error] };
  // email check never runs if name fails!
  const email = validateEmail(data.email);
  if (!email.ok) return { ok: false, errors: [email.error] };
  const age = validateAge(data.age);
  if (!age.ok) return { ok: false, errors: [age.error] };
  return { ok: true, user: { name: name.value, email: email.value, age: age.value } };
}
// User gets: "Name is required" → fixes it → submits again
// Gets: "Email is invalid" → fixes it → submits again  ← bad UX

// APPLICATIVE validation — parallel, collect ALL errors
function validateUserApplicative(data) {
  const results = [
    validateName(data.name),
    validateEmail(data.email),
    validateAge(data.age),
  ];
  const errors = results
    .filter(r => !r.ok)
    .map(r => r.error);
  if (errors.length > 0) return { ok: false, errors };
  return { ok: true, user: {
    name: results[0].value,
    email: results[1].value,
    age: results[2].value,
  }};
}
// User gets ALL 3 errors at once → fixes everything → submits once ← good UX

// Independent API calls — Applicative (parallel)
const [user, settings, perms] = await Promise.all([
  fetchUser(id),        // independent
  fetchSettings(id),    // independent
  fetchPermissions(id), // independent
]);
Apply when
Form validation: collect ALL errors at once so users see everything in one submission (Applicative)
Fetching independent data: user profile + settings + permissions — fetch all in parallel (Applicative)
Step 2 depends on step 1's value: fetchUser then fetchOrdersFor(user.id) — sequential (Monad)
Rule: if you can write the steps in any order without changing correctness, use Applicative. If order matters, use Monad.
Check Your Understanding
You are validating a signup form with 5 fields. The user submits with 3 fields invalid. Which approach gives better user experience?