Local First Is Really About Generalization: Why the Best Systems Stop Recomputing the World
Hatched by Jaeyeol Lee
May 10, 2026
9 min read
3 views
86%
The hidden problem both compilers and collaborative apps are trying to solve
What do a type generalizer in a programming language and a local first application on your laptop have in common?
At first glance, almost nothing. One lives deep inside a compiler, deciding how to safely reuse a value across different contexts. The other lives in the messy reality of notes, documents, and sync conflicts, trying to let people work offline without losing coherence. But both are wrestling with the same fundamental question:
How do you avoid paying the cost of redoing work when the world changes around you?
That question sounds technical, but it is really about intelligence. A system becomes powerful when it can distinguish between what is truly specific and what is safely reusable. If it treats everything as local, it becomes wasteful and brittle. If it treats everything as universal, it becomes wrong. The art lies in finding the exact boundary where a result can be lifted out of a particular context and used again.
That boundary is the secret shared by type generalization and local first software. One is about reusing a computation across program contexts. The other is about reusing a user’s work across device contexts. In both cases, the real challenge is not storage or synchronization. It is generalization: deciding what can be made portable without losing meaning.
Generalization is not abstraction. It is disciplined forgetting.
Most people hear the word generalization and think of abstraction, but they are not the same thing. Abstraction is often about removing detail until a pattern remains. Generalization is more exacting. It asks: which details are essential to the current context, and which can be forgotten because they do not affect future use?
A compiler that generalizes a type is performing a kind of intellectual triage. It examines a value and asks which assumptions are accidental, and which are fundamental. If it is too eager, it produces a type that promises more than the value can deliver. If it is too conservative, it traps a reusable result inside a narrow context and forces needless recomputation later.
That tension exists in every local first product. Suppose you write a note on a train with no network. The app stores it locally, then later syncs it to the cloud and perhaps to another device. The crucial question is not simply “can it sync?” It is: what part of the note is stable enough to survive other contexts, and what part is still bound to the local moment in which it was created?
A timestamp may be local. A paragraph of prose may be portable. A cursor position may be device specific. A collaboration operation may be generalizable if it can be expressed as an intent rather than a raw state change.
This is why local first architecture is not just a user experience philosophy. It is an information theory problem in disguise. The system must create objects that are not only stored, but also generalizable across time, devices, and conflicting histories.
Generalization is the act of making something reusable without pretending it was never specific.
That sentence matters because it captures the central discipline of both domains. You do not eliminate context. You identify the minimum context required for safe reuse, then encode that boundary explicitly.
The real tradeoff: immediacy versus portability
Local first systems are often described in terms of speed, privacy, and offline reliability. Those are all real benefits, but they are downstream effects. The deeper promise is that the user’s work becomes immediate without becoming fragile.
In a traditional cloud centric app, every meaningful action is outsourced to a server. That makes persistence and collaboration simpler in one sense, but it creates a hidden tax: the user’s present action is constantly hostage to a remote context. Latency is not merely a delay, it is a declaration that the system has not yet decided your action is real.
Local first flips that logic. Your device becomes the primary site of truth for immediate interaction, and sync becomes a process of reconciliation rather than permission. This is not just better UX. It is a different epistemology. The app says, in effect, “what you did here is real now, and we will work out how it fits elsewhere later.”
But this only works when the local action is representable in a form that can survive later reconciliation. If the system only stores final states, it is often too rigid to merge well. If it stores operations, it can preserve intent, but then it must know which operations generalize and which do not. That is the same problem as typing a polymorphic function. A result can be reused in many contexts only if its dependencies have been carefully isolated.
Think about a shared document. If two people edit the same paragraph offline, a naive system might compare final text blobs and produce a merge disaster. A better system stores operations such as insert, delete, move, or replace, each with a precise scope. Those operations are like generalized types. They are small enough to be portable, but meaningful enough to compose.
This is the hidden pattern: the more a system can represent intent instead of state, the more it can generalize without losing correctness.
That is also why poor sync systems feel uncanny. They behave as if the world were a single frozen snapshot, when in fact it is a collection of partially overlapping perspectives. Generalization lets a system acknowledge that plurality and still remain coherent.
A useful mental model: context debt
Here is a framework that connects the two domains in a practical way.
Every system carries context debt. This is the amount of hidden information it must remember in order to safely reuse a result later. The higher the context debt, the more fragile the system becomes.
A value with low context debt is easy to move around. It says, “I can function in many places because I rely on very little that is specific.” A value with high context debt says, “I only make sense if you recreate my original environment exactly.”
In programming language terms, a function that depends on too many hidden assumptions cannot be generalized cleanly. In product terms, a note, task, or document that only works inside one device state or one network state cannot be synced gracefully. The system may still function, but every future change will require expensive reconstruction of the original context.
You can think of context debt in four layers:
- Data context: What specific values does this object depend on?
- Temporal context: When was it created, and does time itself matter?
- Relational context: Which other objects or people must agree with it?
- Interpretive context: What does this action mean, not just what does it change?
Good generalization minimizes all four, but never to zero. That is impossible. A general type is not a void. A local first object is not context free. It simply captures the minimum context necessary for valid reuse.
This is why the best systems feel both flexible and trustworthy. They do not erase reality. They model it more carefully.
Portability is not the absence of context. It is the right kind of context made explicit.
Once you see context debt, you start noticing it everywhere. A collaborative editor that cannot represent intent accumulates debt every time it syncs. A type system that cannot isolate what is reusable accumulates debt every time a function crosses a boundary. In both cases, the system becomes more expensive not because it is doing too much, but because it is hiding too much.
Why the future belongs to systems that can say “this is enough”
The deepest connection between these ideas is not technical elegance. It is a philosophy of sufficiency.
A system that generalizes well knows when to stop carrying detail. A local first system knows when to stop depending on the network. Both are exercises in judgment. They reject the fantasy that more context is always better.
This matters because modern software often fails by overcollecting context. Analytics pipelines capture everything but understand little. Collaboration tools preserve every keystroke but lose intent. Build systems remember every file change but struggle to identify the real dependency. The result is a kind of bloated certainty, where the system holds more information than it can actually use.
Generalization offers a counter principle: store only what improves reuse, merge only what preserves intent, and depend only on what can survive change.
That principle has design consequences.
A note app should not treat offline as an error state. It should treat offline as a natural mode in which the user’s intent is fully real, but its broader reconciliation is deferred.
A data model should not be optimized merely for a single canonical database record. It should be optimized for the kinds of transformations the system must survive: edits, merges, moves, renames, duplication, and partial loss of connectivity.
A programming language should not ask developers to micromanage every special case of reuse. It should infer and expose the points where a result can be lifted safely into a broader context.
In each case, the system improves when it can answer a subtle question: what is the smallest complete description that still makes this thing reusable?
That question is more valuable than “how do we store everything?” because it shifts the design target from completeness to resilience. Completeness is a fantasy of perfect capture. Resilience is the practical ability to reuse without regret.
Key Takeaways
- Generalization is disciplined forgetting. Keep only the context that future reuse truly needs.
- Local first is not just about offline mode. It is about making user actions real immediately while preserving enough structure to reconcile them later.
- Context debt is the hidden cost of brittle systems. If an object depends on too much invisible context, it becomes expensive to move, merge, or reuse.
- Represent intent, not just state. Systems that can model operations and meaning generalize better than systems that store final snapshots alone.
- Ask what can survive change. The best designs are built around the smallest complete unit that remains valid across time, devices, and conflicts.
The deeper lesson: intelligence is reuse under constraint
A surprising amount of intelligence, both human and machine, comes down to one ability: recognizing what can be reused safely in a new situation. That is what type generalization does in a compiler. It is also what a well designed local first app must do when it turns local edits into shared reality.
This is why these two subjects belong together more than they first appear to. They both ask how to turn a one time event into something that can travel. They both reject the naive extremes of either total specificity or total abstraction. And they both succeed only when they make context visible enough to be managed, but not so visible that it becomes a burden.
The next time you see a system that feels fast, resilient, and surprisingly elegant, look for this pattern. Somewhere beneath the surface, it is probably doing the hardest work of all: deciding exactly what can be carried forward, and exactly what must be left behind.
That is not just good software design. It is a general theory of making things last.
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 🐣