Programming
Functional Programming
Computer Science
Haskell
Monads

What is a monad?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

A monad is a design pattern in functional programming that provides a structured way to chain operations together while handling side effects (I/O, state, errors, optionality). In practical terms, a monad is a type that implements two operations: wrapping a value (return/unit) and chaining computations (bind/>>=/flatMap). If you have used Promise.then() in JavaScript, Optional.flatMap() in Java, or list comprehensions in Python, you have already used monads.

The Three Monad Laws

A type is a monad if it provides:

  1. return (or unit): Wraps a plain value in the monadic context
  2. bind (or >>= or flatMap): Takes a monadic value and a function that returns a monadic value, chains them together

And satisfies three laws:

haskell
-- 1. Left identity: return a >>= f  ≡  f a
-- 2. Right identity: m >>= return    ≡  m
-- 3. Associativity:  (m >>= f) >>= g ≡  m >>= (\x -> f x >>= g)

In simpler terms: wrapping and then chaining is the same as just calling the function directly, chaining with the wrapper does nothing, and the order of grouping chains does not matter.

The Maybe/Optional Monad

The most intuitive monad handles values that might be absent:

haskell
1-- Haskell
2safeDivide :: Float -> Float -> Maybe Float
3safeDivide _ 0 = Nothing
4safeDivide x y = Just (x / y)
5
6-- Chaining operations that might fail:
7result = Just 10 >>= (\x -> safeDivide x 2) >>= (\y -> safeDivide y 0)
8-- result = Nothing (division by zero propagates automatically)

In Java, this is Optional:

java
1Optional<Double> result = Optional.of(10.0)
2    .flatMap(x -> safeDivide(x, 2.0))    // Just 5.0
3    .flatMap(x -> safeDivide(x, 0.0));   // Empty — short-circuits
4// result = Optional.empty()

In Python (with a simple implementation):

python
1def safe_divide(x, y):
2    return None if y == 0 else x / y
3
4def bind(value, func):
5    if value is None:
6        return None
7    return func(value)
8
9result = bind(bind(10, lambda x: safe_divide(x, 2)),
10              lambda y: safe_divide(y, 0))
11# result = None

The List Monad

Lists are monads where bind maps a function over each element and flattens the results:

haskell
-- Haskell
[1, 2, 3] >>= (\x -> [x, x*10])
-- Result: [1, 10, 2, 20, 3, 30]
python
# Python equivalent using list comprehension (which IS the list monad)
result = [y for x in [1, 2, 3] for y in [x, x*10]]
# [1, 10, 2, 20, 3, 30]
javascript
// JavaScript
[1, 2, 3].flatMap(x => [x, x * 10])
// [1, 10, 2, 20, 3, 30]

flatMap is literally bind for arrays — map then flatten.

The Promise/Async Monad

Promises in JavaScript are monads:

javascript
1// return = Promise.resolve (wraps a value)
2// bind   = .then (chains computations)
3
4Promise.resolve(5)                          // return
5    .then(x => fetchUser(x))                // bind — returns a new Promise
6    .then(user => fetchOrders(user.id))     // bind — chains another async op
7    .then(orders => console.log(orders));

Each .then() takes a value, applies a function that returns a Promise, and chains them — exactly the monad pattern.

The IO Monad (Haskell)

Haskell uses the IO monad to handle side effects in a pure functional language:

haskell
1main :: IO ()
2main = do
3    putStrLn "What is your name?"   -- IO action
4    name <- getLine                  -- bind: extract value from IO
5    putStrLn ("Hello, " ++ name)    -- another IO action

The do notation is syntactic sugar for monadic bind. Without it:

haskell
main = putStrLn "What is your name?" >>= (\_ ->
       getLine >>= (\name ->
       putStrLn ("Hello, " ++ name)))

The Result/Either Monad

Handles computations that can fail with an error:

rust
1// Rust: Result<T, E> is a monad
2fn parse_and_double(s: &str) -> Result<i32, String> {
3    s.parse::<i32>()
4        .map_err(|e| format!("Parse error: {}", e))
5        .and_then(|n| {
6            if n > 1000 {
7                Err("Number too large".to_string())
8            } else {
9                Ok(n * 2)
10            }
11        })
12}
haskell
1-- Haskell: Either
2safeDivide :: Int -> Int -> Either String Int
3safeDivide _ 0 = Left "Division by zero"
4safeDivide x y = Right (x `div` y)
5
6result = Right 100 >>= (\x -> safeDivide x 5) >>= (\y -> safeDivide y 0)
7-- Left "Division by zero"

Why Monads Matter

Without monads, chaining operations that might fail requires nested conditionals:

python
1# Without monads — nested error checking
2user = get_user(id)
3if user is not None:
4    address = get_address(user)
5    if address is not None:
6        city = get_city(address)
7        if city is not None:
8            return city
9return None
10
11# With monadic chaining (flatMap)
12result = (get_user(id)
13    .flat_map(get_address)
14    .flat_map(get_city))

Monads eliminate the pyramid of doom by providing a uniform interface for chaining.

Monads in Common Languages

LanguageMonad Typereturnbind
HaskellMaybe aJust>>=
JavaScriptPromise<T>Promise.resolve.then
JavaOptional<T>Optional.of.flatMap
RustResult<T, E>Ok(v).and_then
ScalaOption[T]Some(v).flatMap
SwiftOptional<T>.some(v).flatMap
C#Task<T>Task.FromResultawait / ContinueWith

Common Pitfalls

  • Overcomplicating the concept: A monad is just a type with flatMap (or bind) and return that follows three laws. You do not need category theory to use monads — you already use them via Promises, Optionals, and lists.
  • Confusing map with flatMap: map transforms the value inside a monad. flatMap transforms and then flattens (unwraps one layer). Using map where you need flatMap gives you nested monads (Optional<Optional<T>>).
  • Monad tutorials: The running joke is that anyone who understands monads immediately loses the ability to explain them. Start with practical examples (Maybe, Promise) rather than category theory.
  • Monads are not about side effects: While the IO monad handles side effects, monads in general are about chaining computations. Lists, optionals, and results are monads with no side effects.
  • Breaking the laws: Custom monad implementations that violate the three laws cause unexpected behavior when composed. Always verify left identity, right identity, and associativity.

Summary

  • A monad is a type with return (wrap a value) and bind/flatMap (chain computations) that follows three laws
  • Common monads: Maybe/Optional (handles absence), List (handles multiple values), Promise (handles async), Either/Result (handles errors)
  • You already use monads: Promise.then(), Optional.flatMap(), list comprehensions, Result.and_then()
  • Monads eliminate nested conditionals by providing a uniform chaining interface
  • The key insight: monads let you compose functions that return "wrapped" values without manually unwrapping at each step

Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions