Locality and simplicity aren’t secrets, they’re fundamentals we underuse. Here’s why they deserve more weight in how we write and ship code.
Productivity Starts With What You Can See
Most developers don’t lose time writing code.
They lose it finding code, understanding code, and being afraid to touch code.
Code with good locality and simplicity shortens all three:
- You don’t have to trace imports to understand behavior.
- You don’t have to reverse-engineer a framework to make a change.
- You don’t have to wait on the one engineer who “knows that part of the system.”
You move faster because the map is smaller, and the roads make sense.
Locality: A Simple Definition
Locality means the code you need to understand is near the code you’re reading.
Not hidden in helpers. Not abstracted behind layers. Just there.
The best code doesn’t hide its behavior. It lays it bare, close to where it matters.
In practice, a good heuristic for locality is this: how many files do you need to open to fully understand a given function’s behavior? If it’s more than 2, you may be losing clarity.
Simplicity Builds Confidence
The fastest developers aren’t necessarily the most experienced.
They’re the ones most confident they can make a change without breaking things.
Local, simple code makes change safe:
- You see what’s happening.
- You understand the impact.
- You test the change without mocking five unrelated systems.
And when you’re not afraid of the codebase, you ship faster.
Abstractions Aren’t Free
Yes, abstraction helps avoid repetition. But it can also slow you down when it:
- Obscures business logic
- Requires reading multiple files to understand one function
- Introduces a new concept just to avoid five lines of duplication
If your abstraction takes longer to understand than the code it replaces, it’s not an optimization. It’s a liability.
When the Rules Should Be Broken
Locality and simplicity aren’t absolute. Sometimes, indirection is worth it.
- A mature system with hundreds of endpoints may benefit from shared logic.
- If you’re building an SDK or public API surface, abstraction helps encapsulate change.
- For logic that’s used across dozens of components, duplication may create more risk than consolidation.
The question is always: does this abstraction improve understanding or reduce it?
Senior developers know the rules. Great ones know when to bend them.
Complexity Compounds: In Code, Process, and Teams
Complexity is corrosive. It spreads across layers: not just in code, but in how we work and how we organize.
- In code, it hides logic and inflates surface area.
- In process, it turns feedback loops into approval chains.
- In teams, it creates silos and slows decisions.
Example: A single shared helper that quietly does too much becomes a dependency for four teams. Now, changing it means coordination across five backlogs. A two-line update takes two weeks.
Now imagine a team that adds a deployment checklist with twelve required steps. What starts as an attempt at safety quickly morphs into friction: every hotfix now needs three approvals and two manual verifications. Developers stop bothering, or stop shipping altogether.
So what does simple process actually look like?
- At a 50-person company: A clear path from dev to prod, with one place to see what’s deploying and who owns it.
- At a 500-person company: Guardrails built into tooling, not meetings. Default behaviors that don’t require policy memos to explain.
Fixing organizational complexity isn’t about cutting corners. It’s about making the right path the easy one.
Before & After: Locality in Practice
Before:
handleUserClick(user.id, event);
...which calls a helper that imports another helper that uses a custom event emitter pattern...
After:
user.markAsActive();
trackClickEvent(user.id);
Clear. Obvious. Local. No indirection, no detective work.
Let’s switch domains for a moment to illustrate how the same principles apply beyond UI code.
Data pipeline example:
Before:
run_pipeline(job_id, input_df)
Which internally builds config objects and dispatches to another worker based on runtime flags.
After:
cleaned = clean_input(input_df)
scored = score_users(cleaned)
export(scored)
Now, the logic is visible. Each step tells you what it does. There’s no need to decode a config file just to understand what happens to the data.
A Quick Productivity Model
Dev productivity = (Time writing code) ÷ (Time understanding code)
Locality and simplicity don’t increase how fast you type.
They reduce the cost of understanding. That’s the unlock.
Try This: A Locality Check
- Open a file that frustrates you.
- Ask: Could a junior dev understand this without asking for help?
- If not, simplify it. Move the logic closer. Delete the unnecessary abstraction.
Want to push this further?
- Create a dashboard of top-changed files. Are they the hardest to understand?
- Audit the "number of jumps" required to trace a feature across layers. Can that number go down?
Productivity doesn’t require a new tool. It starts with your next PR.
This Scales With Your Team
Local, simple code doesn’t just help the author.
It helps the next dev. And the one onboarding next week.
This is how teams stay adaptable and avoid grinding to a halt under pressure.
When people understand what they’re looking at, they move with confidence. They ship with less risk. And they stay longer because the system isn’t punishing them for not knowing everything.
The Skeptic's Take
A senior architect might argue: “But clean architecture requires layers of separation. If we simplify too far, we lose control.”
That’s not wrong. But the goal isn’t to remove every layer. It’s to remove the ones that don’t earn their keep.
Use architecture to protect against complexity, not to introduce it. If a layer exists, it should serve understanding, not obstruct it.
Valid example: A shared logging or metrics library used across dozens of services is a worthwhile abstraction. It isolates concerns, avoids duplication, and creates a consistent way to observe systems. The key is that it has a narrow, well-understood contract and solves a clear, repeatable problem. Its indirection earns its place because it improves operability without obscuring domain logic.
Why Complexity Drains Us
Cognitive science tells us that working memory is limited. Every unresolved reference, every unfamiliar pattern, every unclear dependency adds to that load.
- Extraneous complexity burns energy. It forces your brain to context-switch, trace paths, and simulate logic that could’ve been obvious.
- Locality and simplicity reduce that overhead. They minimize what you need to remember, freeing up brainpower for actual problem-solving.
Cognitive overhead shows up in ways we can measure—longer ramp-up times, slower debugging, and more time spent second-guessing changes. Developer fatigue, error rates, and velocity all correlate with how much mental overhead your codebase demands.
Final Thought: The Best Dev Experience Is Clarity
Developer productivity isn’t about clever abstractions. It’s about removing friction.
Locality removes the hunt.
Simplicity removes the hesitation.
And reducing complexity keeps codebases, processes, and organizations healthy.
When complexity is unavoidable, managing it well becomes critical. Here’s how:
- Clear contracts and ownership boundaries — Each component knows what it owns and what it depends on. No shared ambiguity.
- Documentation at the edges — Capture the interfaces and assumptions between systems, not every line of logic inside them.
- Visual tooling and mapping — Systems should reveal their structure without needing a guide. Graphs, dashboards, or simple flow diagrams can go a long way.
These don’t eliminate complexity—but they contain it, expose it, and keep it from leaking into every developer’s day.
Together, these ideas help teams build software that’s easy to understand, easy to change, and easy to trust.
That’s what real developer velocity looks like.
Top comments (0)