Over the last few articles, we've explored a surprising number of concepts:
reduce()- Transducers
- Functors
- FlatMap
- Monads
- RxJS
scan()- Event Sourcing
- Performance tradeoffs
- Object spread
At first glance, these seem like completely different topics.
Some are functional programming concepts.
Some are architecture patterns.
Some are JavaScript APIs.
Some are performance discussions.
But there is a common thread connecting all of them.
And it's not:
reduce()
It's not:
monads
It's not:
RxJS
The real superpower behind all of them is:
COMPOSITION
Because the best software systems aren't built from giant functions.
They're built from small pieces that work together.
And once you understand that idea, everything from React to Unix to Event Sourcing starts making a lot more sense.
The Biggest Mistake Developers Make
Many developers spend years learning functions.
Senior engineers spend years learning patterns.
The difference matters.
For example:
const usersById =
users.reduce(...)
Interesting.
But not transformative.
The truly valuable question is:
How do I combine small pieces
into larger systems?
That question changes everything.
Because software is ultimately a composition problem.
Why Large Functions Rot
Let's start with something most developers have seen.
function processCheckout(
order
) {
validateOrder(order)
calculateTax(order)
applyDiscount(order)
reserveInventory(order)
generateInvoice(order)
sendConfirmationEmail(order)
updateAnalytics(order)
createShipment(order)
notifyWarehouse(order)
updateCRM(order)
}
Looks harmless.
Now imagine this function after three years.
500 Lines
Multiple Conditions
Feature Flags
Edge Cases
Special Customers
Regional Logic
Eventually:
Nobody Wants To Touch It
The function becomes fragile.
Every change becomes risky.
Small Functions Scale Better
Instead:
const validateOrder =
order => order
const calculateTax =
order => order
const applyDiscount =
order => order
const reserveInventory =
order => order
Each function has:
One Responsibility
This matters because:
Small Pieces
↓
Are Easier To Understand
↓
Easier To Test
↓
Easier To Replace
That is composition.
Unix Understood This Decades Ago
One of the greatest examples of composability isn't JavaScript.
It's Unix.
Consider:
cat logs.txt |
grep ERROR |
sort |
uniq
Each command does one thing.
cat
↓
grep
↓
sort
↓
uniq
Individually:
Not impressive.
Together:
Extremely powerful.
Fifty years later:
We're still using this pattern.
That should tell us something.
React Won Because Of Composition
Many developers think React succeeded because of JSX.
Or hooks.
Or virtual DOM.
I don't think that's the real reason.
React succeeded because of composition.
<App>
<Header />
<Sidebar />
<Dashboard />
</App>
Every component is:
Reusable
Composable
Replaceable
You don't build one giant UI.
You build pieces.
Then compose them.
RxJS Is Composition
Consider:
stream.pipe(
filter(isValid),
map(transform),
scan(reducer, initialState)
)
At first glance:
This looks like operators.
But underneath:
Function
↓
Function
↓
Function
Each operator produces a new stream.
The real value isn't:
map()
The real value is:
The Ability To Combine Them
Again:
Composition.
Event Sourcing Is Composition
In the previous article we saw:
events.reduce(
reducer,
initialState
)
Now imagine:
Events
↓
Projection A
Projection B
Projection C
Projection D
The same event stream can generate:
- Account Balances
- Reports
- Analytics
- Audit Logs
Why?
Because reducers compose.
The architecture becomes flexible because the pieces are composable.
Microservices Done Right
Good microservices are not:
Many Services
Good microservices are:
Composable Services
Each service should be:
Independent
Replaceable
Focused
The goal isn't:
More Services
The goal is:
Better Composition
Unfortunately many teams create:
Distributed Monoliths
which have all the complexity and none of the benefits.
Composition Beats Inheritance
Consider classical inheritance.
class Animal {}
class Bird extends Animal {}
class FlyingBird extends Bird {}
Eventually:
FlyingSwimmingBird
appears.
Then:
FlyingSwimmingHuntingBird
And things become ridiculous.
Composition offers a different approach.
const canFly = {}
const canSwim = {}
const canHunt = {}
Combine capabilities.
Instead of inheriting everything.
This scales far better.
Building A Tiny Utility Library
One of the simplest examples of composition is pipe().
const pipe =
(...fns) =>
input =>
fns.reduce(
(acc, fn) =>
fn(acc),
input
)
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)
)
Output:
36
Why?
2
↓
3
↓
6
↓
36
Small pieces.
Large result.
Real World Example: API Processing
Suppose we're building an API Studio.
Bad:
function processRequest(
request
) {
// 1000 lines
}
Better:
Parse
↓
Validate
↓
Authenticate
↓
Execute
↓
Transform
↓
Format
↓
Visualize
Each stage:
Independent.
Composable.
Replaceable.
Testable.
This architecture scales much better over time.
Real World Example: Payment Processing
Instead of:
function processPayment() {
...
}
Think:
Validate
↓
Fraud Check
↓
Charge Card
↓
Generate Receipt
↓
Notify User
Each step becomes reusable.
You can swap providers.
Add new logic.
Remove old logic.
Without rewriting the entire system.
Real World Example: CI/CD Pipelines
GitHub Actions.
GitLab CI.
Azure DevOps.
All rely heavily on composition.
Checkout
↓
Install
↓
Test
↓
Build
↓
Deploy
Independent stages.
Combined together.
Again:
Composition.
Why Composition Feels So Powerful
Because it gives us leverage.
Imagine:
100 Independent Functions
versus:
1 Giant Function
The first system creates:
Reuse
Flexibility
Scalability
The second creates:
Coupling
Complexity
Fragility
This is why composability appears everywhere.
The Hidden Connection Between Everything We've Learned
Let's revisit the series.
Reduce:
Composable State Transitions
Transducers:
Composable Transformations
Functors:
Composable Mapping
FlatMap:
Composable Container Operations
Monads:
Composable Computations
RxJS:
Composable Streams
Event Sourcing:
Composable Reducers
Different vocabulary.
Same underlying idea.
Pros Of Composable Systems
1. Easier To Test
Small units are easier to validate.
2. Easier To Reuse
Components can appear in multiple places.
3. Easier To Replace
Swap one piece without rewriting everything.
4. Better Scalability
Systems evolve naturally.
5. Better Team Collaboration
Multiple engineers can work independently.
Cons Of Composition
Let's be honest.
Composition is not free.
1. Too Many Small Functions
Overdoing it creates fragmentation.
2. Debugging Can Become Harder
Execution paths may span many layers.
3. Abstraction Overload
Not every problem needs twenty tiny functions.
4. Indirection
Following the flow can take time.
5. Overengineering Risk
Sometimes a simple function is enough.
The Real Lesson
The biggest lesson from this entire series isn't about:
reduce()
or
monads
or
RxJS
Those are merely tools.
The deeper lesson is:
Small Things
That Work Together
Beat
Big Things
That Do Everything
The most successful software systems aren't powerful because of individual functions.
They're powerful because those functions compose.
That's true for:
- React
- Unix
- RxJS
- Event Sourcing
- Microservices
- Modern Frontend Architectures
- Functional Programming
Different ecosystems.
Same principle.
And once you start recognizing composition everywhere, you begin to understand why some systems remain maintainable for decades while others become unmanageable after a few months.
Because ultimately:
Composability is the real superpower.
What's Next?
In the next article we'll move from theory to practice:
Building Your Own Functional Utility Library
We'll implement:
pipe()compose()map()filter()reduce()flatMap()- Transducer helpers
from scratch and explore what those implementations teach us about JavaScript itself.
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 (2)
The Unix pipe example is still the clearest demonstration of this after 50 years. Four commands that individually do nothing interesting, combined into something actually useful.
The con you listed about debugging is the real tradeoff nobody talks about enough. Composable systems are easier to build and test in isolation, but when something breaks across 6 layers the stack trace becomes a nightmare.
I'm curious if the article touches on managing state