Programming After AI: Why System Boundary Taste Matters

Since coding agents came on the scene, a subset of programmers panic about AI replacing their jobs, but I think they're panicking about the wrong thing. The fear seems focused on the mechanical aspects—will AI write better functions than me? Will it debug faster? Will it remember more API endpoints? But I'd argue a tacit skill of senior engineers is knowing where to draw system boundaries. AI can generate code from specifications, but it can't decide which abstractions will make sense, or how a system should evolve as its context changes.
This distinction matters because most programmers spend their careers optimizing for immediate readability, treating code as literature to be consumed by their present-day colleagues (though it might not feel like that at times). But the boundary decisions you make today—how you partition responsibility, where you allow flexibility, which dimensions of change you prepare for—these determine whether your system gracefully adapts or buckles under the weight of accumulated compromises. AI doesn't live with technical debt. It doesn't feel the pain of a poorly chosen abstraction echoing through years of maintenance. It generates clean solutions to well-defined problems–under strict guidance–but problem definition itself remains stubbornly human for the foreseeable future.
The history of data storage offers a surprisingly clear lens for understanding a common instance of this boundary drawing challenge, though most programmers don't realize they're repeating the same fundamental mistakes. Consider how we think about data structures: arrays optimize for sequential access, hash tables for key-based lookup, trees for ordered traversal. Each makes a specific trade-off, committing upfront to particular access patterns while making others expensive or impossible. This is boundary thinking in its most concrete form—you're literally deciding how to organize information in memory, and that organization constrains every future interaction with your data.
Early database systems followed this same rigid pattern. Hierarchical databases like IBM's IMS organized information as trees, forcing you to traverse a single predetermined path to reach any piece of data. The approach made perfect sense if you knew exactly how your data would be accessed. CSS selectors work this way and selecting the DOM hierarchy work this way. Even XML databases with XPath queries suffer the same limitation (for those of you of a specific vintage). But asking "show me all elements with red backgrounds" requires visiting every node. You get blazing performance for the access pattern you optimized for, and awkward workarounds for everything else.
The weakness of hierarchical systems becomes apparent the moment you encounter cross-cutting concerns—requirements that don't respect your carefully constructed boundaries. When you don't know your access patterns in advance, or when users start asking questions that cut across your hierarchies in unexpected ways, you discover that your optimization has become a prison. The system serves its structure rather than serving its users.
Relational databases revolutionized this by essentially deferring the boundary decision. Codd's insight with relational algebra wasn't just about mathematical elegance—it was about recognizing that the most important access patterns are often the ones you can't predict. Instead of hardcoding how you'll slice your data, relational systems let you decide at query time. You can ask for entity.property just as easily as property.entity, or any combination that serves your immediate need. The system adapts to your questions rather than forcing your questions to adapt to the system's structure.
The ability to query across different dimensions for a data set isn't merely a database story—it's a philosophy about system design that most programmers never internalize. The success of relational databases hints at a deeper principle: the most robust systems are those that can be reorganized along dimensions their original designers never considered.
Which brings me to what I think is a surprising insight when I first heard it: entity-component systems (ECS) in games are just relational databases in disguise. Entity-component systems are way of bookkeeping various entities in the game, such as the player, monsters, power up items, etc., but allowed the game to process them by system, such as the physics and rendering.
Games discovered this pattern not through theoretical exploration but through necessity. Traditional object-oriented game engines organize code around conceptual entities—Player class, Enemy class, PowerUp class—and this works beautifully until you need systems that operate across entity types. Physics affects everything, rendering affects everything, AI systems need to query spatial relationships between arbitrary objects. The entity-centric boundaries that make perfect sense to human designers become computational and design bottlenecks.
The design bottleneck can easily happen, when you're trying to find the fun by recombining various sub-systems. When designing a shooting game, it's natural to decide and assume that a camera is fixed on the player, and any AI is attached to the monster. But what if the fun is in a homing missile? Now we need to attach the AI to a projectile. And if the fun is in seeing from the perspective of the homing missile as it's searching its target? Well, that breaks the assumption that the camera subsystem is always on the player.
Casey Muratori's talk on rejecting "compile-time hierarchies" articulates this tension precisely. Object-oriented programming convinced an entire generation that entity-first thinking was universally correct, that domains should always be modeled by drawing boundaries around conceptual objects. This works when your domain naturally clusters around stable entities—Alan Kay's work in molecular biology, where cells and proteins have clear identities, or Bjarne Stroustrup's distributed systems, where processes and machines form natural boundaries. But for most computational problems, entity-first thinking is the wrong choice, a premature optimization that constrains future flexibility.
I conceptualize this alternative access as a "transpose." Instead of organizing around entity-to-property relationships, you use property-to-entity relationships. Instead of asking "what methods does a Player have?", you ask "what entities need physics calculations? What entities need rendering updates?" You're drawing boundaries around capabilities and systems rather than around conceptual objects. The mental shift feels subtle, but the architectural implications are profound.
ECS systems make this transpose concrete. A physics system operates on all entities that have physics components—position, velocity, mass. A rendering system operates on all entities with visual components—sprites, animations, shaders. You're essentially running queries: "give me all entities that have both Position and Velocity components so I can update their motion." The similarity to SQL becomes obvious once you notice it: both systems let you join arbitrary data on the fly rather than pre-committing to rigid hierarchies.
Games generally avoid traditional databases because their computational budget remains extreme—every microsecond matters when you're targeting 60fps with complex simulations. But ECS gives them database-like query flexibility while maintaining cache-friendly performance characteristics. They've independently rediscovered relational thinking, optimized for their specific constraints.
Some games push even further into what I can only describe as inference territory. The Chemistry Engine in Breath of the Wild doesn't just query existing properties—it derives new facts from interactions between materials. Fire plus wood yields burning wood; ice plus fire becomes water. The system knows rules about how properties interact and infers new states dynamically. Baba is You takes this to an almost philosophical extreme: the game mechanics themselves emerge from logical rules that get recomputed as you physically rearrange the rule statements. "Baba IS You" becomes "Rock IS You" when you push the words around. The entire game state derives through inference from a changing set of logical facts.
This suggests a future direction that excites me: systems that can not only query from arbitrary dimensions but infer relationships you didn't even know existed. Datalog and inference engines let you specify facts about your domain and ask "what else must be true?" rather than explicitly computing every possible state. Epic's Verse language, developed with Simon Peyton Jones, hints at this direction—a functional logical language where you can "run functions in reverse," using inference to find inputs that produce desired outputs. Game designers are starting to explore what becomes possible when computation becomes less about explicit control flow and more about declarative reasoning.
I don't expect widespread adoption of inference-based systems soon—the tooling remains esoteric, and most developers are still learning to think in terms of queries rather than objects. But the underlying trend seems clear: as AI commoditizes code generation, the valuable human skill becomes problem modeling. Not the mechanics of translating requirements into syntax, but the deeper challenge of understanding which boundaries will serve you well as requirements evolve.
The programmers who survive will be those who develop intuition for an angle of attack for their problem–one of which is to decide when their domain needs property-first thinking rather than entity-first thinking. The programmers who thrive understand when rigid boundaries serve their purposes and when flexible boundaries justify their complexity. They'll know how to design systems that can be queried from dimensions they haven't yet imagined, because they'll understand that the most important access patterns are often the ones you can't predict.
While AI writes increasingly sophisticated code, humans who understand boundary trade-offs will architect the systems that matter. The question isn't whether AI can code—it's whether you can think about problems in ways that remain valuable as the tools evolve.