← Back to all posts

Cognitive friction in development

Written by Juho Vepsäläinen on . Last updated on .

Tags: productivity

Cognitive friction is the extra effort required to understand what is in front of you. A familiar example is reading text in a language you only partly know: the words may be visible, but meaning arrives slowly if at all.

Developers run into the same problem in code. The compiler may understand the program, but the human reader still has to reconstruct intent, constraints, data flow, and tradeoffs. This post looks at where that effort comes from and how to reduce it.

Sources of cognitive friction for developers

Naming is one of the easiest places to create or remove cognitive friction. Good names do not have to be long, but they should match the level of abstraction around them. Algorithmic code can often use compact mathematical names because the surrounding concepts are abstract. Domain code usually benefits from more specific names because the reader is trying to connect code to business meaning. This is where domain-specific languages (DSLs) may have concrete value.

Friction appears when the abstraction level and the name disagree. Consider a function called processData in a payment system. To discover the real action, the reader has to open the implementation and read the function. In contrast, a function called captureAuthorizedPayment carries more context at the call site. That difference compounds when you return to a codebase months later or search for the place where behavior lives. This may also matter for coding agents that rely on names to infer intent.

Structure matters as much as naming. The more a reader has to jump between branches, callbacks, files, and abstractions, the harder the program is to hold in working memory. McCabe’s cyclomatic complexity gives one measurable view into this, and many static analysis tools can track it.

Complexity metrics are not a substitute for judgment, but they are useful signals. Sometimes the best improvement is not another abstraction, but inlining a small helper so the local story becomes obvious. Performance-sensitive code may have different tradeoffs, which is why measurements, benchmarks, and profiling matter. The point is not to make every function tiny. The point is to make the important behavior easy to follow.

Paradigm mismatch is another source of friction. Some problems fit naturally into declarative descriptions, some into pipelines, some into state machines, and some into plain imperative steps. Frameworks encode these preferences too. When you work with the framework, the code can feel direct. When you fight it, every feature starts to require exceptions and escape hatches.

Mixed paradigms can be a good choice when each part is doing a clear job. For example, a declarative UI can still call imperative code at the edges where the system talks to files, networks, or devices. Friction grows when the boundary is accidental rather than designed.

How to address cognitive friction

Start with naming because it is cheap and visible. Use nouns for things, verbs for actions, and domain terms where they clarify intent. Avoid catch-all names such as data, item, value, or handler when a more specific term is available. A good test is whether a teammate can understand a call site without opening the implementation.

Look at control flow next. Flatten deeply nested logic where you can, return early for invalid cases, and make invariants explicit. Design by contract, popularized by Eiffel, is still a useful mental model: state what must be true before and after an operation. That framing also connects well to property-based testing, where tests explore whether those invariants hold across many generated inputs.

Static analysis can help you see where cognitive friction is accumulating. Complexity scores, lint rules, type checks, dependency graphs, and dead-code detection all reveal different aspects of code shape. A related signal is churn: files that change often deserve extra design attention because small misunderstandings there are expensive.

When delivering features, consider a lightweight quality gate around complexity. It does not have to block every merge, but it should make complexity visible in review. This becomes even more useful with agentic development workflows, because clear constraints give coding tools something concrete to optimize against. In short, guardrails help keep your agents on track, especially when their outputs can vary between runs.

Conclusion

Cognitive friction sits at the center of software work because development is mostly structured understanding. You cannot remove it entirely, but you can make code easier to re-enter, review, modify, and trust.

You can learn more about the sources of friction in software development from the anchor post of this series.