DEV Community

Cover image for You've Been Using Monads Without Realizing It
Amrishkhan Sheik Abdullah
Amrishkhan Sheik Abdullah

Posted on

You've Been Using Monads Without Realizing It

Let's get the scary part out of the way.

The word:

Monad
Enter fullscreen mode Exit fullscreen mode

has probably scared away more developers than any other term in programming.

The moment somebody mentions Monads, the conversation usually becomes:

Category Theory
Endofunctors
Monoids
Kleisli Composition
Higher-Kinded Types
Enter fullscreen mode Exit fullscreen mode

Most developers immediately think:

This isn't for me.
Enter fullscreen mode Exit fullscreen mode

Which is unfortunate.

Because Monads are actually one of the most practical abstractions in software engineering.

And if you've ever used:

Promise.then(...)
Enter fullscreen mode Exit fullscreen mode

or

Array.flatMap(...)
Enter fullscreen mode Exit fullscreen mode

or

RxJS switchMap(...)
Enter fullscreen mode Exit fullscreen mode

then you've already been using Monads.

You just didn't know the name.


Why Developers Fear Monads

Monads suffer from a marketing problem.

Most concepts in programming are taught from examples.

For example:

const doubled =
  [1,2,3].map(
    x => x * 2
  )
Enter fullscreen mode Exit fullscreen mode

Nobody starts by defining Arrays using abstract mathematics.

We start with examples.

Monads are often taught backwards.

The theory comes first.

The intuition comes last.

Let's fix that.


The Problem That Creates Monads

Imagine we have:

const users = [
  {
    id: 1,
    orders: [101, 102]
  },
  {
    id: 2,
    orders: [103]
  }
]
Enter fullscreen mode Exit fullscreen mode

If we do:

users.map(
  user => user.orders
)
Enter fullscreen mode Exit fullscreen mode

we get:

[
  [101, 102],
  [103]
]
Enter fullscreen mode Exit fullscreen mode

Nested arrays.

We saw this in the previous article.

To solve it:

users.flatMap(
  user => user.orders
)
Enter fullscreen mode Exit fullscreen mode

Result:

[
  101,
  102,
  103
]
Enter fullscreen mode Exit fullscreen mode

FlatMap removes the extra container layer.


The Same Problem Exists Everywhere

Arrays are not special.

Consider Promises.

Promise.resolve(10)
  .then(x => {
    return Promise.resolve(
      x * 2
    )
  })
Enter fullscreen mode Exit fullscreen mode

What do we get?

Not:

Promise<Promise<number>>
Enter fullscreen mode Exit fullscreen mode

Instead:

Promise<number>
Enter fullscreen mode Exit fullscreen mode

The nesting disappears.

Why?

Because Promise.then behaves like FlatMap.


The Monad Recipe

Every Monad has two operations.

A way to put a value into the container:

Value
↓
Container<Value>
Enter fullscreen mode Exit fullscreen mode

For Promises:

Promise.resolve(10)
Enter fullscreen mode Exit fullscreen mode

For Arrays:

[10]
Enter fullscreen mode Exit fullscreen mode

For RxJS:

of(10)
Enter fullscreen mode Exit fullscreen mode

And a way to chain operations that return containers:

flatMap
Enter fullscreen mode Exit fullscreen mode

or equivalent.

That is essentially the Monad pattern.


Arrays Are Monads

Most developers never hear this.

But Arrays are Monads.

Let's prove it.

Create a value:

const value = [10]
Enter fullscreen mode Exit fullscreen mode

Chain computations:

const result =
  value.flatMap(
    x => [x * 2]
  )
Enter fullscreen mode Exit fullscreen mode

Output:

[20]
Enter fullscreen mode Exit fullscreen mode

Now chain again:

const result =
  [10]
    .flatMap(x => [x * 2])
    .flatMap(x => [x + 1])
Enter fullscreen mode Exit fullscreen mode

Output:

[21]
Enter fullscreen mode Exit fullscreen mode

Each step returns a container.

FlatMap composes them.

Monad behavior.


Promises Are Monads

This one surprises people.

Promise.resolve(10)
  .then(x =>
    Promise.resolve(
      x * 2
    )
  )
  .then(x =>
    Promise.resolve(
      x + 1
    )
  )
Enter fullscreen mode Exit fullscreen mode

Result:

Promise<21>
Enter fullscreen mode Exit fullscreen mode

Not:

Promise<
  Promise<
    Promise<21>
  >
>
Enter fullscreen mode Exit fullscreen mode

Promises flatten automatically.

Which means Promises satisfy the same pattern.


Why Async/Await Feels Natural

Async/await became popular because it hides Monad plumbing.

This:

const user =
  await fetchUser()

const orders =
  await fetchOrders(
    user.id
  )
Enter fullscreen mode Exit fullscreen mode

feels straightforward.

Underneath:

fetchUser()
  .then(user =>
    fetchOrders(user.id)
  )
Enter fullscreen mode Exit fullscreen mode

is doing Monad composition.

JavaScript developers use Monads daily without realizing it.


RxJS Is Monad City

Consider:

searchText$
  .pipe(
    switchMap(
      text => api.search(text)
    )
  )
Enter fullscreen mode Exit fullscreen mode

Without switchMap:

Observable<
  Observable<SearchResult>
>
Enter fullscreen mode Exit fullscreen mode

With switchMap:

Observable<SearchResult>
Enter fullscreen mode Exit fullscreen mode

The nesting disappears.

Again.

The same abstraction.


Why Monads Exist

Imagine a world without them.

Every operation returning a container would create another layer.

Array<Array<Array<T>>>

Promise<Promise<T>>

Observable<Observable<T>>
Enter fullscreen mode Exit fullscreen mode

Composition would become painful.

Monads solve this.

They allow:

Container Operations
↓
Compose Naturally
↓
Without Nesting
Enter fullscreen mode Exit fullscreen mode

That is their real purpose.

Not mathematics.

Not academic theory.

Composition.


Real World Example: Database Queries

Suppose:

getUser(id)
Enter fullscreen mode Exit fullscreen mode

returns:

Promise<User>
Enter fullscreen mode Exit fullscreen mode

And:

getOrders(userId)
Enter fullscreen mode Exit fullscreen mode

returns:

Promise<Order[]>
Enter fullscreen mode Exit fullscreen mode

Without flattening:

Promise<
  Promise<Order[]>
>
Enter fullscreen mode Exit fullscreen mode

Everywhere.

With Promise.then:

getUser(id)
  .then(user =>
    getOrders(user.id)
  )
Enter fullscreen mode Exit fullscreen mode

One clean chain.

This is why Monads matter.


Real World Example: API Pipelines

authenticate()
  .then(session =>
    fetchProfile(session)
  )
  .then(profile =>
    fetchPermissions(profile)
  )
Enter fullscreen mode Exit fullscreen mode

Every step returns a Promise.

Yet the code remains flat.

Because Promise.then keeps flattening.


Monad Laws (Don't Panic)

Just like Functors have laws.

Monads have laws.

The good news?

They're surprisingly sensible.


Left Identity

Putting a value into a Monad and immediately chaining should behave the same as calling the function directly.

Conceptually:

Wrap
↓
Chain

=

Direct Call
Enter fullscreen mode Exit fullscreen mode

Right Identity

Wrapping and unwrapping should not change behavior.


Associativity

This:

value
  .flatMap(f)
  .flatMap(g)
Enter fullscreen mode Exit fullscreen mode

should behave like:

value.flatMap(
  x => f(x)
    .flatMap(g)
)
Enter fullscreen mode Exit fullscreen mode

This consistency is what makes composition reliable.


Why Most Monad Tutorials Fail

Because they start with:

Monad
↓
Theory
↓
Examples
Enter fullscreen mode Exit fullscreen mode

This article does the opposite.

Arrays
↓
Promises
↓
RxJS
↓
FlatMap
↓
Monad
Enter fullscreen mode Exit fullscreen mode

By the time we reached the word Monad, you already understood the idea.


Pros Of Monads

1. Powerful Composition

Container-producing functions chain naturally.


2. Eliminate Nesting

Avoid:

Promise<Promise<T>>

Array<Array<T>>

Observable<Observable<T>>
Enter fullscreen mode Exit fullscreen mode

3. Common Across Ecosystems

Arrays.

Promises.

RxJS.

Streams.

Functional libraries.


4. Predictable Behavior

The laws provide consistency.


5. Foundation Of Modern Async Programming

Promises rely heavily on Monad-like behavior.


Cons Of Monads

1. Terrible Terminology

The word scares people.


2. Often Explained Poorly

Many tutorials focus on theory before intuition.


3. Easy To Overcomplicate

Simple ideas become academic discussions.


4. Different Libraries Use Different Names

flatMap
then
switchMap
mergeMap
chain
bind
Enter fullscreen mode Exit fullscreen mode

Same family.

Different vocabulary.


5. Can Feel Abstract Initially

Until you connect them to familiar tools.


The Real Lesson

The funny thing about Monads is that they're not particularly complicated.

The terminology is.

The mathematics can be.

The idea itself isn't.

If you've used:

Promise.then(...)
Enter fullscreen mode Exit fullscreen mode

or

Array.flatMap(...)
Enter fullscreen mode Exit fullscreen mode

or

switchMap(...)
Enter fullscreen mode Exit fullscreen mode

you've already been using Monads.

You simply learned the practical version before learning the name.

And honestly, that's probably the best way to learn them.


What's Next?

In the next article we'll leave pure FP terminology behind and explore a topic that surprises almost every JavaScript developer:

RxJS Is Just Arrays Over Time

Because once you see that relationship, most RxJS operators become dramatically easier to understand.


About The Author

Hi, I'm Amrish Khan.

I enjoy building developer tools, exploring software architecture, and writing about the deeper ideas behind everyday programming concepts.

I'm also building Aruvix — a growing ecosystem of local-first developer tools designed to process data directly in the browser without unnecessary uploads.

Here's a detailed blog on Aruvix:

https://dev.to/amrishkhan05/aruvix-the-ultimate-offline-first-developer-toolkit-e0i

You can follow my work and thoughts here:

Portfolio:
https://www.amrishkhan.dev

LinkedIn:
https://www.linkedin.com/in/amrishkhan

GitHub:
https://www.github.com/amrishkhan05

If you enjoyed this article, consider following for more deep dives into JavaScript, architecture, local-first software, and performance engineering.

Top comments (0)