LLMs are good at some programming tasks and bad at others. The difference often comes down to how much hidden context the task requires.

Jonathan Blow’s distinction between shallow state and deep state is a useful way to think about this.

Shallow state

Shallow state is visible in a small piece of code: a function signature, local variables, and the immediate logic.

An LLM can usually fix a type error, write a utility function, or refactor a self-contained method. The relevant context fits into the prompt, and the task has a clear boundary.

Deep state

Deep state is the collection of decisions a codebase has absorbed over time.

A billing service may assume prices are stored in cents. A cache may assume a specific shape for user records. A webhook may depend on how tenants are modeled. Each decision is understandable by itself, but a new feature may need to respect all of them at once.

Nobody wrote these constraints in one place. They are spread across code, tests, documentation, and the memories of people who built the system.

LLMs struggle here. They can read many files, but they do not build the same long-term model as a developer who has worked in the system for years. They produce code that looks correct in one file and breaks an assumption somewhere else.

Reducing hidden state

Waiting for a better model will help, but system design matters too.

  1. Use clear module boundaries. Keep responsibilities narrow and make modules communicate through explicit interfaces.
  2. Match modules to the product domain. Code is easier to understand when its terms match the language used by the business.
  3. Make state changes visible. Limit mutation and route important changes through clear paths.
  4. Document why decisions exist. Comments explain code. Architecture notes should explain constraints and tradeoffs.
  5. Test interactions between parts of the system. Integration tests catch violations that look correct locally.
  6. Add useful observability. Logs, metrics, and traces make runtime behavior easier to reconstruct.

These practices already make software easier for people to understand. They also reduce how much hidden context an AI tool must recover before it can make a safe change.

AI will work better in codebases that require less guessing.