DEV Community

Cover image for Building Your Own Functional Utility Library
Amrishkhan Sheik Abdullah
Amrishkhan Sheik Abdullah

Posted on

Building Your Own Functional Utility Library

One of the best ways to understand a programming concept is to build it yourself.

Over the last several articles we've discussed:

  • Reduce
  • Transducers
  • Functors
  • FlatMap
  • Monads
  • RxJS
  • Composition

And while those concepts sound sophisticated, something surprising happens when you start implementing them.

Most of them are tiny.

Really tiny.

In fact, many of the abstractions that power modern functional programming can be implemented in a few lines of JavaScript.

And once you build them yourself, they stop feeling magical.

They start feeling obvious.

So let's build a small functional utility library from scratch.

Not because you'll replace existing libraries.

But because the implementation teaches more than the API ever will.


Why Build Your Own?

Most developers use libraries like:

  • Lodash
  • Ramda
  • fp-ts
  • RxJS

without understanding the ideas underneath.

That's completely fine.

But implementing the primitives yourself teaches:

How Composition Works

How Reduce Works

How FlatMap Works

How Transducers Work

How Functional Pipelines Work
Enter fullscreen mode Exit fullscreen mode

Which is far more valuable than memorizing APIs.


The Simplest Utility: Identity

Let's start with the most boring function imaginable.

const identity = x => x
Enter fullscreen mode Exit fullscreen mode

Usage:

identity(5)
Enter fullscreen mode Exit fullscreen mode

Output:

5
Enter fullscreen mode Exit fullscreen mode

Not impressive.

But surprisingly useful.

Many FP abstractions rely on identity.


Implementing pipe()

One of the most useful utilities in any FP library.

const pipe =
  (...fns) =>
  input =>
    fns.reduce(
      (acc, fn) => fn(acc),
      input
    )
Enter fullscreen mode Exit fullscreen mode

Usage:

const addOne =
  x => x + 1

const double =
  x => x * 2

const square =
  x => x * x

const process =
  pipe(
    addOne,
    double,
    square
  )

console.log(
  process(2)
)
Enter fullscreen mode Exit fullscreen mode

Output:

36
Enter fullscreen mode Exit fullscreen mode

Flow:

2
↓
3
↓
6
↓
36
Enter fullscreen mode Exit fullscreen mode

This is composition in action.


Implementing compose()

Some developers prefer right-to-left composition.

const compose =
  (...fns) =>
  input =>
    fns.reduceRight(
      (acc, fn) => fn(acc),
      input
    )
Enter fullscreen mode Exit fullscreen mode

Usage:

const process =
  compose(
    square,
    double,
    addOne
  )
Enter fullscreen mode Exit fullscreen mode

Produces:

2
↓
3
↓
6
↓
36
Enter fullscreen mode Exit fullscreen mode

Same result.

Different direction.


Building map()

Let's implement our own map.

const map =
  transform =>
  array =>
    array.map(transform)
Enter fullscreen mode Exit fullscreen mode

Usage:

const double =
  map(x => x * 2)

double([1,2,3])
Enter fullscreen mode Exit fullscreen mode

Output:

[2,4,6]
Enter fullscreen mode Exit fullscreen mode

Notice something.

We made it:

Curried
Enter fullscreen mode Exit fullscreen mode

which allows composition.


Building filter()

const filter =
  predicate =>
  array =>
    array.filter(predicate)
Enter fullscreen mode Exit fullscreen mode

Usage:

const activeOnly =
  filter(
    user => user.active
  )
Enter fullscreen mode Exit fullscreen mode

Again:

Composable.


Building reduce()

const reduce =
  (reducer, initial) =>
  array =>
    array.reduce(
      reducer,
      initial
    )
Enter fullscreen mode Exit fullscreen mode

Usage:

const sum =
  reduce(
    (acc, n) => acc + n,
    0
  )

sum([1,2,3])
Enter fullscreen mode Exit fullscreen mode

Output:

6
Enter fullscreen mode Exit fullscreen mode

Creating Reusable Pipelines

Now things get interesting.

const processUsers =
  pipe(
    filter(
      user => user.active
    ),
    map(
      user => user.name
    )
  )
Enter fullscreen mode Exit fullscreen mode

Usage:

processUsers(users)
Enter fullscreen mode Exit fullscreen mode

This begins to feel like a miniature Ramda.


Building flatMap()

From an earlier article:

const flatMap =
  transform =>
  array =>
    array.flatMap(
      transform
    )
Enter fullscreen mode Exit fullscreen mode

Usage:

flatMap(
  user => user.roles
)
Enter fullscreen mode Exit fullscreen mode

Output:

[
  "admin",
  "editor",
  "viewer"
]
Enter fullscreen mode Exit fullscreen mode

Again:

Tiny implementation.

Huge usefulness.


Implementing a Tiny Maybe Monad

Let's build a minimal Maybe type.

const Maybe = value => ({
  map(fn) {
    return value == null
      ? Maybe(null)
      : Maybe(fn(value))
  },

  flatMap(fn) {
    return value == null
      ? Maybe(null)
      : fn(value)
  },

  value() {
    return value
  }
})
Enter fullscreen mode Exit fullscreen mode

Usage:

Maybe("John")
  .map(
    name =>
      name.toUpperCase()
  )
  .value()
Enter fullscreen mode Exit fullscreen mode

Output:

JOHN
Enter fullscreen mode Exit fullscreen mode

You've just built a Monad.

Without category theory.


Implementing Transducers

Let's revisit Transducers.

Map Transducer:

const mapping =
  fn =>
  reducer =>
  (acc, value) =>
    reducer(
      acc,
      fn(value)
    )
Enter fullscreen mode Exit fullscreen mode

Filter Transducer:

const filtering =
  predicate =>
  reducer =>
  (acc, value) =>
    predicate(value)
      ? reducer(acc, value)
      : acc
Enter fullscreen mode Exit fullscreen mode

Compose:

const transducer =
  filtering(
    x => x % 2 === 0
  )(
    mapping(
      x => x * 2
    )(
      (acc, value) => {
        acc.push(value)
        return acc
      }
    )
  )
Enter fullscreen mode Exit fullscreen mode

Single pass.

No intermediate arrays.

Exactly as discussed earlier.


Building A Tiny Stream

Let's build a very small Observable.

const Stream = (
  subscribe
) => ({
  subscribe,

  map(fn) {
    return Stream(
      observer =>
        subscribe(
          value =>
            observer(
              fn(value)
            )
        )
    )
  }
})
Enter fullscreen mode Exit fullscreen mode

Usage:

const stream =
  Stream(observer => {
    observer(1)
    observer(2)
    observer(3)
  })

stream
  .map(x => x * 2)
  .subscribe(
    console.log
  )
Enter fullscreen mode Exit fullscreen mode

Output:

2
4
6
Enter fullscreen mode Exit fullscreen mode

Tiny implementation.

Huge insight.


What We Learned

A surprising pattern emerges.

Most abstractions are:

Much Simpler
Than Their Names Suggest
Enter fullscreen mode Exit fullscreen mode

Functors:

map()
Enter fullscreen mode Exit fullscreen mode

Monads:

flatMap()
Enter fullscreen mode Exit fullscreen mode

Transducers:

Composable Reducers
Enter fullscreen mode Exit fullscreen mode

RxJS:

Composable Streams
Enter fullscreen mode Exit fullscreen mode

Composition:

Functions Combined
Enter fullscreen mode Exit fullscreen mode

The terminology sounds intimidating.

The implementations are often tiny.


Real World Example

Suppose we're processing API responses.

const processResponse =
  pipe(
    filter(
      item => item.active
    ),
    map(
      item => ({
        id: item.id,
        name: item.name
      })
    )
  )
Enter fullscreen mode Exit fullscreen mode

Clean.

Composable.

Reusable.


Pros Of Building Your Own Utilities

1. Deep Understanding

You learn the underlying mechanics.


2. Better Debugging

Abstractions stop feeling magical.


3. Improved Architecture Skills

You begin designing composable systems.


4. Stronger JavaScript Knowledge

Language fundamentals become clearer.


5. Better Library Evaluation

You understand what libraries are actually doing.


Cons Of Building Your Own Utilities

1. Not Production Ready

Use mature libraries when appropriate.


2. Missing Edge Cases

Production libraries handle many corner cases.


3. Maintenance Cost

You now own the code.


4. Performance Optimizations

Libraries often contain years of tuning.


5. Reinventing The Wheel

Sometimes unnecessary.


The Real Lesson

The biggest surprise in functional programming isn't how complicated it is.

It's how simple most of the building blocks actually are.

The scary names:

Functor

Monad

Transducer
Enter fullscreen mode Exit fullscreen mode

often hide tiny implementations.

The real value isn't the code.

The real value is the mindset.

Learning how to build these utilities teaches you how to think in composition.

And that skill transfers everywhere:

  • Frontend Development
  • Backend Systems
  • Event Processing
  • API Design
  • Architecture

Because ultimately:

Great software isn't built from giant frameworks.

It's built from small pieces that work together.


What's Next?

In the next article we'll tackle one of the most misunderstood concepts in software engineering:

Why Abstractions Leak

Because every abstraction eventually breaks down.

And understanding why helps you design better systems.


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)