devto 2026-06-25 원문 보기 ↗
Over the last few articles, we've explored a surprising number of concepts:
reduce()scan()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.
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.
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.
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.
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.
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.
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.
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:
Why?
Because reducers compose.
The architecture becomes flexible because the pieces are composable.
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.
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.
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.
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.
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.
GitHub Actions.
GitLab CI.
Azure DevOps.
All rely heavily on composition.
Checkout
↓
Install
↓
Test
↓
Build
↓
Deploy
Independent stages.
Combined together.
Again:
Composition.
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.
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.
Small units are easier to validate.
Components can appear in multiple places.
Swap one piece without rewriting everything.
Systems evolve naturally.
Multiple engineers can work independently.
Let's be honest.
Composition is not free.
Overdoing it creates fragmentation.
Execution paths may span many layers.
Not every problem needs twenty tiny functions.
Following the flow can take time.
Sometimes a simple function is enough.
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:
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.
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()from scratch and explore what those implementations teach us about JavaScript itself.
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.