Hey everyone, Alex here. Welcome back to another edition of Coding with Alex on sysseder.com. Today, I want to talk about something a bit different, but deeply personal and intensely relevant to how we build software.
I was browsing Hacker News this morning and ran across a fascinating headline: "People who can't picture anything are rewriting the science of imagination." It’s about aphantasia—the inability to visualize mental images. If you ask someone with aphantasia to imagine a red apple, they don't see a shiny, red fruit floating in a dark void. They know what an apple is, they can list its attributes (red, crisp, sweet, has a stem), but there is no internal monitor playing a video of it. They live in a world of pure data, without the rendering engine.
This blew my mind when I first learned about it, because as developers, we are constantly told that we need to "visualize" system architectures, "see" the flow of data through a pipeline, or "picture" how a complex recursion algorithm unwinds. It turns out that a significant chunk of our engineering community—some estimates say up to 3-5% of the population, and possibly higher in STEM fields—actually has aphantasia. They are writing world-class code, designing massive cloud infrastructures, and solving complex algorithmic problems without ever "seeing" a single line of code in their minds.
So, how do they do it? And more importantly, what can those of us who do (or don't) visualize learn from this to write cleaner, more maintainable code? Let’s dive into the cognitive architecture of coding, how aphantasia changes the way we debug, and how we can design our developer tools and codebases to be friendlier to every kind of mind.
The Cognitive Stack: Visual vs. Conceptual Thinking
To understand how this impacts software development, we need to look at how different brains process abstract concepts. In software engineering, we deal with highly abstract, non-physical structures. A Kubernetes cluster, a relational database schema, or an OAuth2 authorization flow don't exist in the physical world. We must build mental models of them.
For a visual thinker, a mental model might look like a dynamic, 3D architectural diagram. They might "see" a microservice sending an HTTP POST request to another service, visualizing the payload moving along a pipeline like a package on a conveyor belt.
For a developer with aphantasia, the mental model is entirely conceptual, relational, and propositional. It functions more like a highly indexed, relational database or a graph database. They don't "see" the package on the conveyor belt; instead, they understand the system as a set of logical assertions and state transitions:
Service Ahas a dependency onService B.- An event
Etriggers a transition fromState XtoState Y. - The data payload must conform to
Schema Z.
This is pure, non-sensory propositional logic. And as it turns out, this is exactly how computers actually process information. Computers don't have eyes; they have logic gates. In many ways, the mind of a developer with aphantasia is running a highly optimized, text-based terminal, bypassing the resource-heavy "graphical user interface" of the visual cortex.
How Aphantasia Shapes Coding Styles
Because non-visualizers cannot rely on "looking" at a mental picture of their codebase to understand how it fits together, they often develop coding habits that favor high readability, strict organization, and low cognitive load. These habits, coincidentally, align perfectly with clean coding best practices.
1. Extreme Modularity and Low Coupling
If you can't visualize a giant, sprawling codebase, you absolutely must keep things modular. A visual thinker might tolerate a 2,000-line "God Class" because they can mentally map its regions. A non-visual developer will likely find this intolerable because it exceeds the capacity of their working memory. They will break the code down into small, single-responsibility functions that can be reasoned about in isolation.
2. Self-Documenting Code and Explicit Naming
When your mental model is built on semantic relationships rather than visual spatialization, names matter immensely. Ambiguous variable names like temp, data, or handler are the enemies of conceptual clarity. Non-visualizers tend to write highly descriptive, explicit names that tell a clear story of what the entity is and what it does.
3. Heavy Reliance on Types
Strong, static typing is a massive boon for conceptual thinkers. A robust type system acts as a mathematical proof of how different parts of the system interact. It provides compile-time guarantees that replace the need to "double-check" the system flow in your head.
Let's look at a quick comparison. Imagine a visual-heavy JavaScript approach versus a highly structured, type-safe TypeScript approach that relies on explicit domain modeling.
// Example A: Implicit, visual/dynamic flow (Harder to reason about conceptually)
function processOrder(order) {
const user = getUser(order.userId);
if (user.status === 'active') {
const total = order.items.reduce((acc, item) => acc + item.price, 0);
if (total > 100) {
applyDiscount(order, 0.1);
}
saveToDb(order);
sendEmail(user.email, "Order Processed");
}
}
Now, let's look at how we can refactor this into a highly declarative, typed, and modular structure. This style is incredibly easy to reason about because each step is an explicit, self-contained transformation of data, requiring zero mental "visualization" to trace:
// Example B: Declarative, typed, and modular (Low cognitive load)
type ActiveUser = { id: string; email: string; status: 'active' };
type OrderItem = { price: number; name: string };
type Order = { id: string; userId: string; items: OrderItem[]; discount?: number };
function calculateTotal(items: OrderItem[]): number {
return items.reduce((sum, item) => sum + item.price, 0);
}
function shouldApplyBulkDiscount(total: number): boolean {
return total > 100;
}
function applyDiscount(order: Order, rate: number): Order {
return { ...order, discount: rate };
}
// Pure domain logic, easy to reason about in isolation
export function handleOrderPlacement(
order: Order,
user: ActiveUser,
saveFn: (o: Order) => void,
notifyFn: (email: string) => void
): void {
const total = calculateTotal(order.items);
const finalOrder = shouldApplyBulkDiscount(total)
? applyDiscount(order, 0.1)
: order;
saveFn(finalOrder);
notifyFn(user.email);
}
By breaking the code into small, pure functions and using explicit types, we remove the need to hold a complex state machine in our working memory. You don't need a "mind's eye" to see what shouldApplyBulkDiscount does; its input, output, and logic are fully self-contained and transparent.
Debugging Without a Map
Debugging is another area where cognitive differences shine. How do you find a bug in a complex system if you can't visualize the data flowing through it?
Visual thinkers often debug by "walking through" the execution path in their head, trying to see where the state gets corrupted. While this can be fast for simple bugs, it is highly error-prone for complex, asynchronous, or concurrent systems.
Developers with aphantasia often rely on more systematic, scientific debugging methods because they have to. They lean heavily on:
- Observability and Structured Logging: If you can't see the state in your head, you must make it visible on the screen. This means writing rich, structured logs (JSON) with proper trace IDs.
- Test-Driven Development (TDD): Writing failing tests first establishes a concrete, external safety net. It externalizes the mental model. If the tests pass, the logical assertions of the system are correct, regardless of what we can or cannot visualize.
- Static Analysis Tools: Leveraging linters, type checkers, and dependency graph generators to map out the codebase externally.
In essence, the absence of an internal visualizer forces the developer to build superior external tooling and write more testable code. The codebase itself becomes the map.
Designing Dev Tools for Every Brain
As team leads, architects, and open-source contributors, we should design our projects, documentation, and tools to accommodate both visual and non-visual thinkers. This neurodiversity is a strength, but only if our tooling supports it.
For the Visual Thinkers: Diagrams as Code
Visual thinkers love architecture diagrams. However, hand-drawn diagrams in Miro or Lucidchart often go out of date the moment they are saved. To bridge the gap, use Diagrams-as-Code tools like Mermaid.js or PlantUML. This allows non-visual, text-oriented thinkers to maintain diagrams using clean, declarative text files inside the git repository, which then render visually for those who need them.
Here is a simple Mermaid.js diagram representing our order process. It's stored as plain text in markdown, but renders as a visual flowchart:
```mermaid
graph TD
A[Receive Order] --> B(Calculate Total)
B --> C{Total > 100?}
C -- Yes --> D[Apply 10% Discount]
C -- No --> E[Keep Original Price]
D --> F[Save to Database]
E --> F
F --> G[Send Confirmation Email]
```
For the Non-Visual Thinkers: Document the "Why," Not Just the "What"
When writing documentation or code comments, avoid vague visual metaphors. Instead, focus on invariants, preconditions, postconditions, and architectural decisions. A great way to do this is through Architectural Decision Records (ADRs). ADRs document the historical context, the trade-offs considered, and the final decision in a clean, text-based format that appeals directly to logical, relational reasoning.
Conclusion: The Diversity of the Developer Mind
The realization that we don't all think the same way is incredibly humbling. The fact that some developers can build complex microservice architectures or write intricate rendering engines (ironically) without being able to picture a single pixel is a testament to the incredible adaptability of the human mind.
Whether you have a vibrant, 4K mental cinema running in your head, or your mind is a quiet, text-based terminal running on pure, elegant logic, there is a place for your style of thinking in software engineering. By understanding these differences, we can write code that is more modular, self-documenting, and robust—making our codebases easier to navigate for everyone, regardless of how their mind's eye is configured.
Do you visualize your code, or do you think in pure conceptual relationships? Have you ever worked with someone who had a completely different cognitive style than yours? Let's chat about it in the comments below!
Until next time, happy coding!
— Alex