The Same Problem Hides in Your App and Your Compiler: When Structure Becomes a Map of Possibility

Jaeyeol Lee

Hatched by Jaeyeol Lee

May 07, 2026

10 min read

87%

0

What if navigation and reasoning are the same design problem?

Most people think app navigation is about finding buttons, and compiler design is about catching errors. That view is too small. Both are really about making a system legible without making it rigid. One helps a human move through a product, the other helps a machine move through a space of types, dependencies, and constraints. In both cases, the hard part is not adding more power. It is deciding how much structure is enough for the next decision to become obvious.

That tension sits underneath nearly every good interface and every good compiler: how do you guide exploration without collapsing it into a maze or a straight line? If you overstructure, users feel trapped and the system becomes brittle. If you understructure, they drift, get lost, and make mistakes. The best systems do something more subtle: they expose a graph of possibilities that feels like a path.

That is why the connection between mobile navigation patterns and graph based type checking is more than an accidental analogy. They are two versions of the same intellectual move. Both replace a flat sequence with a living map.


Linear systems fail when the world is not linear

A mobile app often pretends the user has a single goal in mind, then asks them to drill through screens until they arrive there. But real behavior is rarely so neat. People browse, backtrack, compare, abandon, and re-enter through a different door. The moment a product assumes a straight line, it starts to feel hostile to actual human cognition.

The same is true in programming languages. Traditional type checking can feel like a long, rigid pipeline: declarations go in, errors come out. But once code becomes deeply interdependent, that picture breaks down. Types refer to types, constraints ripple across modules, and a change in one place can alter the meaning of many others. A linear pass is not enough to capture a system that is fundamentally relational.

The deeper lesson is this: the object we are designing is not the path, but the dependency structure underneath the path. In an app, that means menu hierarchy, task flow, history, back navigation, and context. In a compiler, that means type dependencies, inference relationships, and the order in which information becomes available. A good system does not flatten those dependencies. It makes them navigable.

The moment a design assumes the world is a line, it starts hiding the very structure people need to succeed.

A useful mental model is to think of linear design as a hallway, and graph based design as a city. In a hallway, there is one obvious direction and one obvious destination. In a city, there are many routes, landmarks, neighborhoods, and shortcuts. The city is harder to build, but easier to inhabit, because it matches how real decisions happen.


Graphs are not just data structures, they are user experiences

A graph sounds technical, but at heart it is a promise: you can move through complexity without losing orientation. That promise matters whether the traveler is a person tapping a phone or a compiler resolving type relationships. Graph based type checking works because it acknowledges that the meaning of one piece of code depends on many others, and that those dependencies can be revisited when new information appears. Good navigation patterns do the same for product experience.

Think about a shopping app. A crude linear flow says: open app, search, inspect product, add to cart, checkout. But actual shoppers often move like this: start with a search, open several products, return to the list, save one, compare two, visit a category page, then maybe buy later from a saved item. The app is not merely a funnel, it is a network of choices. Navigation that respects this reality gives users multiple stable entry points, persistent context, and easy backtracking.

Now think about code. A naive compiler design tries to resolve everything in one direction. But a graph based approach can represent dependencies explicitly, then revisit nodes as information becomes available. That matters when a type in one file depends on a type alias in another, which in turn depends on a generic constraint defined elsewhere. The compiler is not guessing linearly. It is traversing a dependency map.

This is the same insight in two domains: context is not a bonus, it is the mechanism of comprehension. Users understand where they are by seeing how screens relate. Compilers understand what code means by tracing how types relate. In both cases, the system becomes better when relationships are first class, not accidental.

Here is the subtle leap: a graph is not simply more complex than a list. A graph is more honest. It admits that there are many valid next steps, that meaning is distributed, and that the system must support returning, revising, and reinterpreting. That is why the best navigation is often not the shortest route, but the route that preserves orientation.


The real design choice is between certainty and recoverability

It is tempting to believe that the best interface is one that removes ambiguity. Likewise, it is tempting to believe that the best compiler is one that resolves everything immediately. But ambiguity is not the enemy. Irrecoverability is.

A well designed app does not need to eliminate every choice. It needs to make each choice reversible, understandable, and safe enough to try. That is why back buttons, breadcrumbs, tabs, and persistent state matter. They turn exploration into a low risk activity. The user can venture outward because they trust they can return.

Graph based type checking embodies the same philosophy. When the compiler works over dependencies as a graph, it can propagate information, detect cycles, and revisit nodes as needed. This does not make the system magically simple. It makes it recoverable. It can cope with incomplete information because it knows where uncertainty lives and how to revisit it.

This leads to a more useful principle than “keep it simple”: make complexity inspectable. A simple system hides complexity by deleting it. A resilient system exposes enough structure that complexity can be navigated without panic. That is what good navigation does for people, and what good type checking does for code.

Imagine a museum. A bad museum has one long corridor and tiny labels. A good museum has clear zones, maps at every junction, and enough cues that visitors can choose their own path without feeling lost. The artwork is the same in both cases. The difference is whether the organization respects the way humans actually build understanding: through landmarks, alternatives, and loops, not just sequence.

The same applies to compilers. The code is the same. The difference is whether the compiler architecture respects the way meaning emerges from dependencies, not just from order.


A framework for designing navigable complexity

If navigation and type checking are both about managing relational structure, what can we learn from combining them? One useful framework has four parts: entry points, constraints, feedback, and revision.

1. Entry points: where does orientation begin?

A good system offers more than one way in. In a mobile app, that might mean a home tab, search, notifications, and deep links. In a compiler, it means being able to start from declarations, references, or inferred relationships rather than forcing a single parsing story.

Entry points should match different user intents. Some people arrive with a destination in mind. Others are still discovering the space. The best systems do not punish either mode.

2. Constraints: what prevents disorientation?

Graph based systems are not freeform. They are bounded by edges, dependencies, and rules. Good navigation patterns work the same way. Tabs, headers, and hierarchical groupings are not decorative. They are constraints that reduce cognitive load.

A constraint is useful when it narrows the space of plausible next moves without hiding possibilities. That is why a clear category structure can be more helpful than endless scrolling, and why a compiler that understands dependency graphs can provide more precise feedback than one that merely reports a failure somewhere downstream.

3. Feedback: what does the system reveal when you move?

In an app, feedback includes active states, breadcrumbs, and the ability to see where you came from. In a compiler, feedback includes error localization, dependency tracing, and the ability to show which node caused a cascade.

Without feedback, a graph is just complexity. With feedback, it becomes a map. The user or developer sees the consequences of a move, which is what makes learning possible.

4. Revision: how easily can the system absorb change?

This is where graph thinking becomes most powerful. In real products, state changes. In real code, dependencies change. Systems that can revise gracefully are the ones that last.

A mobile app that loses context every time the user goes back forces them to rebuild mental models. A compiler that cannot intelligently revisit affected nodes forces the whole system to be rechecked in crude ways. Revision is not a failure mode. It is the normal shape of intelligent interaction.

A great system is not one that never changes. It is one that can change without collapsing the user’s or developer’s model of the world.


Why this matters beyond UX and compilers

This connection reaches beyond design and language tooling. It describes a broader pattern in modern life: we increasingly work inside systems whose meaning is distributed across relationships, not located in one place.

We see this in project management, where tasks depend on teams, timelines, and blockers. We see it in knowledge work, where one document points to another and one decision reshapes many others. We see it in AI systems, data pipelines, and organizational charts. Everywhere, the challenge is the same: how to help people move through dependency heavy environments without pretending those dependencies do not exist.

That is why the best abstractions are often graph shaped. They allow local action while preserving global structure. You can think about one screen, one function, one page, or one task without forgetting how it fits into the larger whole. This is the essence of scalable design: local clarity with global coherence.

The reason this feels so difficult is that humans prefer stories, but systems are often networks. Stories are linear, with a beginning, middle, and end. Networks are recursive, with loops, cross links, and feedback. Good products and good tools bridge that mismatch. They give us a story to follow while secretly organizing the world as a graph.

A mobile app’s bottom navigation bar, for example, is not just a menu. It is a declaration of the app’s major regions, the stable neighborhoods users can return to. A compiler’s dependency graph is not just an internal implementation detail. It is a declaration of what the language considers related, what may be inferred, and what must be revisited when something changes. In both cases, architecture is a theory of attention.


Key Takeaways

  1. Design for relationships, not just sequences. If users or systems must revisit context, a graph is a better model than a line.
  2. Make complexity recoverable. The goal is not to eliminate complexity, but to make it inspectable, reversible, and safe to explore.
  3. Offer multiple entry points. Different users arrive with different intents, so navigation should support discovery and direct action.
  4. Preserve orientation during revision. Whether it is a changed screen state or a changed type dependency, the system should explain what changed and why.
  5. Treat feedback as navigation. Good error messages, active states, breadcrumbs, and dependency traces are not extras, they are the map.

The map is the message

We often praise systems for being fast, elegant, or powerful. But the deeper quality that makes them usable is neither speed nor power. It is navigability. Can a person or compiler move through the system, keep track of where they are, understand what depends on what, and recover when the structure changes?

That is the shared problem hiding inside mobile navigation and graph based type checking. Both are attempts to transform complexity from a wall into a terrain. And once you see that, you start to notice the same challenge everywhere: the best systems do not merely execute decisions. They make decisions intelligible.

That may be the most underrated standard in design and engineering. Not just can the system do the thing, but can it show us the shape of the thing while it is doing it? The answer is what separates a tool you use from a world you can actually inhabit.

Sources

← Back to Library

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 🐣