Why the Best Interfaces Behave Like Functions
Hatched by
May 08, 2026
10 min read
5 views
72%
The hidden agreement behind every good interface
What do a reactive UI and a higher-order function have in common? At first glance, almost nothing. One is about pixels, clicks, and keeping the screen honest. The other is about passing functions into functions, a topic that can feel abstract, almost mathematical. But they are united by a deeper idea that most programmers feel before they can name it: good software is often not about doing the work directly, but about designing relationships that do the work for you.
That is the real tension. We usually think of interfaces as places where humans talk to machines, and functions as tools that make code run. Yet the most elegant systems in JavaScript do something stranger. They make change itself manageable. They turn complexity into a pattern of agreements: when state changes, the DOM updates; when a function is handed to another function, control becomes composable.
The surprising connection is this: both reactive assignments and higher-order functions are strategies for delegating responsibility without losing control. One delegates rendering to the runtime, the other delegates behavior to reusable abstractions. In both cases, the programmer stops micromanaging and starts defining rules.
The best code is not the code that does the most. It is the code that makes the right things happen automatically.
Why direct control is overrated
Beginners often imagine programming as a sequence of commands: tell the computer exactly what to do, in exactly the right order, and it will behave. That style works until the system grows. Then every new feature becomes a new place where you must remember to update something else, and the codebase turns into a web of manual bookkeeping.
A reactive assignment reduces that burden by changing the question from, “How do I update the DOM?” to, “What state should the UI reflect?” Once the state changes, the DOM follows. The important move is not the update itself, but the binding between state and interface. That binding is what removes repetition and protects correctness.
Higher-order functions solve the same problem in a different domain. Instead of writing a dozen nearly identical functions, you write one function that accepts behavior as input, or returns a customized function as output. You stop controlling every step directly and instead build a structure that can accept variation.
Consider two common patterns:
- Reactive UI:
countchanges, and the screen updates. - Higher-order utility: a function like
map,filter, or a custom callback handler receives logic and applies it repeatedly.
Both patterns replace fragile manual steps with a contract. In the first case, the contract is between state and the DOM. In the second, it is between a function and the behavior it is given. The programmer is no longer writing every action by hand. Instead, they are designing a system that knows how to react.
This matters because most bugs are not dramatic failures. They are missed updates, duplicated logic, stale assumptions, and inconsistent state. Direct control creates those bugs because the developer must remember everything. Indirection, when used well, creates resilience because the system remembers for you.
A useful mental model: state, behavior, and synchronization
There is a simple way to connect these ideas: think in terms of state, behavior, and synchronization.
- State is what is true right now.
- Behavior is what should happen under certain conditions.
- Synchronization is the mechanism that keeps different parts aligned.
Reactive assignments are synchronization tools. They say: when the state changes, recompute the representation. Higher-order functions are behavior tools. They say: instead of hardcoding one path, let the caller provide the behavior or let the callee return a behavior customized to context.
This distinction is powerful because it helps you see why some code feels brittle and some code feels alive. Brittle code often confuses these roles. It mixes state updates with rendering decisions, or mixes business logic with incidental repetition. Elegant code keeps them distinct, then ties them together through a well-defined mechanism.
Imagine a restaurant. The kitchen does not constantly walk to every table asking whether the food should still be hot. The dining room does not independently decide what ingredients to cook. Each part has a role, and the system works because there is a protocol for keeping them aligned.
A reactive assignment is like a dining room display that updates automatically when the kitchen changes the order status. A higher-order function is like a master recipe that accepts a variable ingredient or technique and produces a family of dishes from one template. The common idea is not automation for its own sake. It is coordination through abstraction.
Abstraction is not hiding complexity. It is choosing the smallest interface that can preserve a useful relationship.
That is why these ideas belong together. They are both about reducing the cost of coordination. They make it cheaper to say, “When this changes, that should change too,” and cheaper to say, “This behavior should be reusable across many cases.”
The deeper connection: reactivity is a higher-order idea in time
Here is the part that usually goes unnoticed: reactive UI and higher-order functions are cousins because both are forms of second-order thinking.
A first-order operation says what to do now. A second-order operation says what should happen when something else happens, or what should happen when a rule is supplied later. In one case, the function is not just doing work, it is accepting behavior as data. In the other, the UI is not just displaying data, it is declaring a dependency on data.
That means reactivity is, in a sense, a higher-order relationship spread across time.
A normal function might say:
function greet(name) {
return `Hello, ${name}`;
}
A higher-order function says:
function withGreeting(formatter) {
return function(name) {
return formatter(name);
}
}
This lets behavior be parameterized.
A reactive assignment says something like:
let count = 0;
let doubled = count * 2;
When count changes, doubled changes automatically. This lets dependency be parameterized.
Both are about avoiding stale logic. In the first, you avoid hardcoding one behavior for all situations. In the second, you avoid hardcoding one rendering moment for all times. The abstraction is different, but the philosophy is identical: let the system derive what can be derived.
This is why highly expressive code often feels less imperative and more declarative. It is not telling the computer every intermediate step. It is telling the computer which relationships matter. Once those relationships are explicit, the runtime can take over the bookkeeping.
That shift is profound. It changes the programmer from a laborer of updates into an architect of dependencies.
The practical payoff: fewer bugs, clearer intent, better composition
The real value of this shared idea is not elegance in the abstract. It is that relationships compose better than instructions.
When you write direct update logic, every new feature adds another place where something can go wrong. You must remember to update the DOM after each state change. You must remember to pass the right callback through multiple layers. You must remember that one helper function depends on another in a subtle way.
When you use reactive assignments and higher-order functions well, you invert that burden. You define the dependency once and let it flow.
For example, think about a shopping cart UI. Without reactivity, changing the item count means manually updating the total price, the badge in the corner, maybe the button state, and perhaps some accessibility text. With reactivity, the total price is derived from the item list, the badge is derived from the count, and the UI stays consistent because each piece depends on the same underlying state.
Now think about a validation system. Without higher-order functions, you might write one function for required fields, one for email format, one for password strength, and so on, each with similar structure. With higher-order functions, you can build validators that accept rules and return specialized checks. The repeated structure becomes a reusable machine.
These are different domains, but they solve the same engineering problem: how do you express change once instead of many times?
That is also why they make code easier to read. The strongest code often announces its dependencies clearly. It says, “This value depends on that one.” It says, “This behavior can be customized by passing in a function.” A reader can inspect the shape of the system instead of reverse-engineering a sequence of mutations.
There is a subtle but important lesson here: simplicity does not always mean fewer lines. Sometimes it means fewer obligations. You should not have to remember to do five things manually when one relationship can guarantee them all.
How to think like this in your own code
If you want to use these ideas well, stop asking, “How do I make this work?” and start asking two different questions:
- What should always stay in sync?
- What behavior should be injectable or reusable?
The first question leads you toward reactive design. The second leads you toward higher-order design. Together, they push you toward systems that are more declarative, more testable, and less repetitive.
A practical rule of thumb:
- If you are manually updating something after every change, ask whether it should be derived instead.
- If you are duplicating logic with minor variations, ask whether a function could accept the varying part as input.
- If you are passing values through several layers just to use them once, ask whether the dependency is being expressed too late.
- If a component or function feels hard to reuse, inspect whether its responsibilities are too tightly coupled.
The goal is not to use abstraction everywhere. The goal is to use the right abstraction at the right boundary. A higher-order function can be overkill if the variation is trivial. Reactive derivation can be unnecessary if a value is genuinely independent. The art is in recognizing where synchronization or parameterization will remove ongoing mental work.
Here is the test I use: if a line of code exists only to remember something the system could have inferred, it is a candidate for removal. If a function exists only because a similar function already exists with one variable part, it is a candidate for higher-order extraction.
This way of thinking gradually shifts your instincts. You stop building piles of instructions and start designing relationships that stay true.
Key Takeaways
- Prefer relationships over repetition. If one value logically depends on another, derive it instead of updating it manually.
- Treat behavior as something you can pass around. Higher-order functions are a clean way to isolate variation from structure.
- Think in contracts, not commands. The best abstractions define what should happen when inputs or state change.
- Look for stale bookkeeping. Repeated manual updates and duplicated logic are signs that your code is doing memory work the runtime could do better.
- Design for synchronization. Whether in UI state or reusable functions, aim for systems where correctness is preserved by structure, not by vigilance.
The real lesson: make change cheap
The deepest connection between these ideas is not technical, it is philosophical. Good software design is often about making change cheap. Not because change is rare, but because it is inevitable. State will change. Requirements will change. Behavior will vary. If your code depends on remembering every consequence of change, it will eventually break under its own weight.
Reactive assignments make change cheap by keeping the interface aligned with state. Higher-order functions make change cheap by turning variation into an input rather than a rewrite. Both are examples of a broader discipline: build systems that absorb change instead of fighting it.
That is why these ideas feel so satisfying when they click. They are not just tricks for cleaner code. They are reminders that software can be shaped around relationships instead of routines. Once you see that, you start noticing it everywhere: in event handlers, in derived values, in reusable helpers, in component design, in the architecture of entire applications.
And perhaps that is the most useful reframe of all. The question is no longer, “How do I make the computer do this step?” The better question is, “What relationship should the computer preserve for me?” When you can answer that clearly, both the DOM and your functions become easier to trust.
Sources
Hatch New Ideas with Glasp AI 🐣
Glasp AI allows you to hatch new ideas based on your curated content. Let's curate and create with Glasp AI :)
Start Hatching 🐣