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.
답글 남기기