Table of Contents
What Is Code?
13 April 2026
Unmesh Joshi
Unmesh is a Distinguished Engineer at Thoughtworks, based in Pune, India. He is the author of
Patterns of Distributed Systems.
Rebecca Parsons
Rebecca is a Technology Strategy Consultant, board member and co-author of Building Evolutionary
Architectures
Martin Fowler
Martin is the host of [Link], the author of Refactoring, and the Chief Scientist at
Thoughtworks.
Contents
Two Aspects of Code
Domain Modeling and Step-by-Step Discovery of the Vocabulary
Where the Vocabulary Comes From
The Role of Programming Languages
Domain-Driven Design and Agile Wisdom
Using the Vocabulary Once It Exists
Conclusion
For much of the history of software engineering, the answer to this question
seemed obvious. Code was the thing engineers wrote: instructions expressed in
a programming language that told machines what to do. Progress was measured
by how efficiently those instructions could be produced, compiled, and
deployed.
That understanding is no longer sufficient.
With modern LLMs capable of generating large amounts of executable code
quickly, the act of writing code has become cheap in a way it never was before.
This forces a deeper question: if writing code is no longer the bottleneck, what
is code actually for?
Two Aspects of Code
Code has always served two distinct but intertwined purposes.
First, code is a set of instructions to a machine. It directs computation, moves
data, interacts with storage, and coordinates execution. In the LLMs era, this is
the part being commoditized.
Second, code is a conceptual model of the problem domain. This is the “design”
aspect—the messy process of deciding what the program should do and the
algorithm of how it should do it. This reflects how the author understands the
space. The abstractions chosen and the names used encode assumptions and
intent.
The activity we call coding is where these two aspects meet.
Domain Modeling and Step-by-Step
Discovery of the Vocabulary
Making the conceptual model explicit is a deeper aspect of coding, driven by the
domain and the use cases the system is meant to address. Every domain comes
with established processes, practices, and a shared vocabulary. What we call a
domain can be both functional and technical. There are functional domains like
retail and finance, and technical domains like the web, mobile, and distributed
data systems.
Coding for a domain is fundamentally an act of translation. The engineer maps
the domain vocabulary onto the vocabulary of technical domains. In doing so, a
new vocabulary is also built using the constructs provided by a programming
language. There are concepts like logs, repositories, quorums, transactions, and
specific concepts like money. Concepts become types, relationships become
interfaces, rules become invariants, and workflows become compositions.
Crucially, this structure does not emerge fully formed. The precise names of
variables, the boundaries of methods, and the hierarchy of classes are
discovered step by step. The right abstraction often is not obvious upfront; it
reveals itself only as you continually mold and refactor the code against
real-world constraints. When used well, the code slowly becomes a readable,
highly specific representation of the domain itself.
Where the Vocabulary Comes From
Where does this vocabulary in code come from? A lot of it comes from a strong
grasp of technical and business domains. So this domain expertise is
indispensable. Just because someone can read or generate English words does
not mean the deeper grasp of the vocabulary exists.
Someone doing web development needs to have a strong grasp of web
architecture, the semantics of web methods, the universal caching potential of
GET, and the implications of those semantics. Someone who does not know that
will not architect complex systems well. The same is true in other domains. The
vocabulary is not just a collection of labels. It carries meaning, constraints, and
design consequences.
The second important aspect is that a lot of this vocabulary is built through
iterative sessions where we write code and reflect on it. Techniques like TDD
are excellent for this iterative development of the vocabulary. They help us
discover the right names, the right abstractions, and the right boundaries by
forcing continuous feedback between the model and its behavior.
The Role of Programming Languages
It is crucial that we are not just passive reviewers of generated code or code
written by someone else. The very act of thinking deeply about code often
happens only when we are actively engaged in writing it. Programming
languages are not neutral vessels for expressing thought after the fact; their
constructs and constraints themselves become thinking tools. The design
constraints provided by different programming languages help shape our
thinking. The channels and lightweight threads of Go, the object-oriented
model of Java, or the ownership model of Rust all push us to see structure,
boundaries, and trade-offs in particular ways. In that sense, programming
languages do not just help us express a design. They also help us discover it.
Domain-Driven Design and Agile Wisdom
It is notable that current discussions about LLMs-assisted coding often say little
about domain-driven design or domain complexity. This is because these
concerns depend on practices that are harder to automate: close collaboration
with domain experts, a common vocabulary, and short feedback loops.
In an era where LLMs can instantly generate entire architectures, the
temptation is to accept a static, one-shot model. But this skips the vital process
of incremental discovery. Misunderstandings can be masked longer, only to
surface later at a much greater cost. The discipline required to discover the
right names and iteratively evolve a domain model remains deeply human and
social.
Working software is not just the goal; it is also a learning tool.
Using the Vocabulary Once It Exists
Once a strong vocabulary is built, a lot of coding is about using it to build use
cases. This is where good libraries and good foundational code shine. A typical
way this works is to build a DSL to make using this vocabulary or these
abstractions easy and close to natural language.
LLMs are excellent at this. They provide a natural-language interface to your
abstraction vocabulary. The best part is that if you have executable code behind
your vocabulary of abstractions, it itself acts as an excellent guardrail for the
LLM to fix its mistakes. Good abstractions, executable behavior, tests, types, and
invariants all help constrain the model and make its output more useful.
In that sense, strong foundational code becomes even more important in the
age of LLMs. Once the vocabulary of a system is well formed, coding becomes
less about producing raw syntax and more about using a well-developed
conceptual language to build reliable software.
Conclusion
The role of coding is not disappearing. But it is changing.
As LLMs makes code generation cheaper, the mechanical act of writing
instructions becomes less central. What becomes more important is making the
conceptual model explicit, discovering the right vocabulary, and refining that
vocabulary through iteration, domain expertise, and feedback. This is also why
programming languages continue to matter deeply. We are not meant to be
passive reviewers of generated code. The act of writing code is itself part of how
we think.
Code is still instructions for a machine. But it is also a model of understanding.
In the LLMs era, that second role becomes even more important. The future of
coding is not just writing more code faster. It is building better conceptual
models, better vocabularies, and better foundations on top of which both
humans and LLMs can work.
Significant Revisions
13 April 2026: initial draft