This post is the third in a series about the philosophy of the architect-builder Christopher Alexander. If you missed the first two posts, you can find them here:
In the previous post, we began an investigation of how Alexander’s fifteen properties of living structure apply to software development. This post continues that thread.
Recall that these 15 properties are derived from Christopher Alexander’s study of structures that have the quality he called life. This lifelike quality connects to biological life in at least three ways:
Its structure bears a mathematical resemblance to that of biological life: e.g. it is fractal (see levels of scale in the previous post) and replete with local symmetries.
It makes us feel more “alive” in its presence: that is, more comfortable, more free to be truly ourselves, and more aware that by being ourselves we are connecting with something eternal that is shared by all humanity.
It correlates with structures that work well — structures that are stable, self-maintaining, and able to grow and evolve. This last point is the strangest, the hardest to grapple with rationally, and possibly the most contentious. It is also the one I find most interesting for the purposes of software development.
If we can reach this quality in software systems — if we can make software that is “alive” in this sense — then we will have made something really worthwhile: software that fits us, that can grow with us, and that makes us feel at home.
This is not a hypothetical: the quality of life in software has been reached, probably thousands of times, in the last few decades. The problem is that most of the systems that had it are no longer part of our daily lives. Either they are gone forever, or else (at best) they are museum pieces.
The problem with good software is that it fits us so well, its quality is unnoticeable. We rarely think of it, or realize how much it’s doing for us, until it is gone. As Zhuangzi said, a comfortable belt is one you forget you’re wearing.1
As humanity exits the railgun of progress that was the twentieth century, one of our many challenges will be to create economic systems that allow us to let things be, when we don’t yet know how to improve them further. Maybe I’ll write more about this in future posts. At present, I don’t really have any concrete suggestions.
Okay, I think that’s enough tangential musing for now. Here is the muse you were promised:
5. Positive Space

Neighboring centers help each other come to life when they are shaped so each one is positive—relatively simple, convex, and locally symmetrical. Positive centers are easy to perceive as foreground figures. They aren’t mere leftover scraps. They have presence; they feel coherent and purposeful. In order for positive space to be present, even the “background” of a design must be positive.

What does it mean for code to evince this property? Recall that the major centers in code are tokens, statements, blocks, functions, objects, and modules. A center in code is positive when it is coherent within itself, uncomplicated by outside concerns nosing in. Functions, methods, classes are inward-looking. Parameter names, function names, and comments don't refer to the functions' callers. It becomes easier to understand each center when it is coherent, centered in itself, even though it is related to and supported by many other centers.
Positively shaped code is neighborly. It takes its shape while allowing its neighbors to take theirs.
Here’s a simple example: a removePrefix function, from my book-compiler mdsite
. You can probably understand what this code does, and perhaps imagine using it in your own software, without knowing anything about the program it’s from.
export function removePrefix(s: string, prefix: string): string {
if (s.startsWith(prefix)) {
return s.slice(prefix.length);
} else {
return s;
}
}
In all these cases, the positiveness of the space — what we might also call the convexity and compactness of the centers which form — is the outward manifestation of internal coherence in the physical system.
—Christopher Alexander, The Phenomenon of Life, p. 262
Positive shape is related to the balanced abstraction principle, which says that each center in code should be written at a consistent level of abstraction. For example, code that reads and writes files should just know about files; it shouldn’t also know about application-specific details like what data is in those files. Conversely, code that deals with concepts in the application domain shouldn’t know about details of infrastructure like files, HTTP requests, or databases. Mixing levels of abstraction compromises positive shape, making code harder to understand and modify. You have to read more and can reuse less.
Positive shape has a pleasant symbiosis with unit testing. Because positively-shaped centers are easy to conceptualize and reason about in isolation, they are easy to test, and it is easy to see that the tests specify the right behavior. You can compose positively-shaped centers and have confidence that the whole will do what you expect, even without comprehensive integration tests.
Positively-shaped code often feels like it could be part of the language or the standard library. It “brings the language up to meet the problem,” abstracting away the nitty-gritty details and distilling the code to its essence. Positive shapes intensify precision and clarity.
Good abstractions are positive in this way. As Dijkstra put it:
The purpose of abstraction is not to be vague, but to create a new semantic level in which one can be absolutely precise.
—Edsger Dijkstra
Katrina Owen gives a good example of what code looks like when it is not positively shaped, in “What’s in a Name: Anti-patterns to a Hard Problem.” She calls this specific antipattern an idea fragment.
# Bad code; negative shape
def prev_or_next_day(date, date_type)
date_type == :last ? date.prev_day : date.next_day
end
This Ruby method comes from a meetup-scheduling app. Dissociated from its context, it makes little sense. It’s unlikely to help you chunk the larger program into brain-sized pieces. You basically have to memorize its implementation and recall it whenever you see the name prev_or_next_day
.
Most negatively-shaped code is like this. It seems to have tendrils of other ideas creeping into it. There’s no way to remove it from its context, either mentally or actually, because it’s too tightly bound to the idiosyncratic needs of its callers. And the code that calls negatively-shaped code is damaged as well: because the negatively-shaped dependencies aren’t coherent abstractions, you have to understand their internals before you can make sense of the calling code.
Positive space, then, is the property that appears in code when we can reason about each part in isolation.
6. Good Shape
Christopher Alexander’s concept of good shape is confusingly similar to positive space. Even he has trouble defining it:
When I began looking for living structures, I was surprised to find out how often, mixed with other properties, there was an element that seemed to defy analysis: the works contained elements with the most gorgeous, beautiful, powerful shapes. Sometimes this beauty of shape seemed subtle, complex, beyond analysis. I became aware of a special quality that I began to think of as good shape, but could not very easily explain it, or define it.
— The Phenomenon of Life, p. 179
He analyses the concept thus:
It is easiest to understand good shape as a recursive rule. The recursive rule says that the elements of any good shape are always good shapes themselves. [...] In addition, we note that the simplest and most elementary good shapes are made from elementary figures.
Have you ever seen code that had this feeling? Code that was simple, elegant, powerful, self-evident — and yet, on close inspection, seemed to be made out of almost nothing?
Here is an example of the kind of code that impresses me with its good shape:
const actors = take(movies)
.and(flatMap(get("castMembers")))
.and(deduplicate)
.and(sort(descending(by(get("name")))))
.result
This bit of JavaScript is made of eight independent functions, the most complicated of which can be written in five lines. Each function has an intense focus that gives it good shape. The whole thing has good shape because — well, just look at it. You can tell exactly what it’s doing. I don’t even need to describe it. The fact that these simple functions can be composed in such an intricate way, and that the whole thing works exactly as you would expect, is marvelous.
You can call it beautiful code when the code makes it look like the language was made for the problem.
—Ward Cunningham
Bonus example: watch Brian Kernighan write a spell-checker in one line of shell (demo starting ~5 minutes in):
7. Local Symmetries
Christopher Alexander’s principle of symmetry echoes the empirical principle that we should assume the world is uniform until we have evidence of nonuniformity.2 Alexander sees symmetry as the application of this principle to design: symmetrical shapes are simpler than asymmetrical ones (bilateral symmetry trivially gives you 50% compressibility) so symmetry must be the default.
Complexity (in the bad sense) consists of distinctions which unnecessarily complicate a structure. To get simplicity, on the other hand, we need a process which questions every distinction. Any distinction which is not necessary is removed. To remove a distinction we replace it by a symmetry.
[. . .] There is an intimate and fundamental connection between arbitrariness, necessity, and symmetry, which says, in a nutshell, this. Everything in nature is symmetrical unless there is a reason for it not to be. When this law is violated, we feel that something is unnatural, and that is the way in which symmetry plays such a fundamental role.
—The Process of Creating Life, p. 469
Alexander was not, however, particularly fond of the global bilateral symmetry that characterizes classical architecture. His experience was that differences in the site, or in the purpose of each wing of the building, tended to motivate asymmetry at larger scales. So instead, he emphasized local symmetry. Each smaller-scale center can be symmetrical, even if the whole building is not.
If there’s an equivalent of bilateral symmetry in code, it’s probably most apparent in if-else statements. Consider the following:
// Example 1:
function foo() {
if (someCondition) {
return "a"
}
return "b"
}
// Example 2:
function foo() {
if (someCondition) {
return "a"
} else {
return "b"
}
}
The addition of the else
in Example 2 doesn't impact the meaning of the program, but it does enhance its symmetry. This symmetry lets us read the code breadth-first, outside-in. If we collapse the blocks in both examples, we see:
// Example 1:
function foo() {
if (someCondition) { ... }
return "b"
}
// Example 2:
function foo() {
if (someCondition) { ... }
else { ... }
}
Only in the symmetrical Example 2 is it clear that the two branches are mutually exclusive.3
In Go, we sometimes see the following pattern, which shuns even the asymmetry of the else
keyword, and makes false
as primary as true
:
switch someCondition {
case true: return "a"
case false: return "b"
}
…and that’s about as close as we ever get to bilateral symmetry in programming. More frequently, we see translational symmetry, where a template-like structure is repeated several times. Here is some Ruby that downloads a code repository given a URL:
downloader =
case URI(url).scheme
when "https"
HttpsDownloader
when "ftp"
FtpDownloader
when "git"
GitDownloader
else
raise ArgumentError.new("Unsupported protocol in URL: '#{url}'")
end
downloader.download(url)
The repeating template here is:
when "value"
DownloaderClass
The repeating structure makes it easy to see which class each string corresponds to. It’s also easy to imagine extending this code with a new type of downloader.
To drive the point home, here is some asymmetrical code that does the same thing:
# Bad code; no symmetry
if https_url?(url) || url.start_with?("ftp://")
Downloader.get(url)
elsif git_ssh_url?(url)
Git.clone(url)
end
Here, we have a Downloader
module that’s responsible for two different kinds of URLs, and a Git
module that’s responsible for the other kind. Also, the way we check for an FTP URL is different from the way we check for HTTPS and git URLs. These superficial differences obscure the underlying similarities between all the types of downloads.
Asymmetry makes code harder to read. It hides regularities of behavior within irregular structures. Symmetry, on the other hand, makes similar things look similar, so the important differences stand out.
The overall principle is this: similar or analogous behaviors should be encoded as similar / analogous structures. When there is nothing motivating the structures to be different, we should let them be the same. And more than that: when faced with complexity, we should try to unearth whatever symmetries we can. Bringing symmetry to light, and polishing it until it shines, is how we make the meaning of code luminously apparent.
8. Deep Interlock and Ambiguity
Alexander observed that adjacent centers often “hook into” one another where they meet — forming a type of thick boundary (see the previous post). For example, a city plaza might have arcades along its edges. These arcades form part of the public space of the plaza, but they are also a kind of outdoor room or passageway for the building to which they connect. The two centers, the building and the square, blend and interpenetrate in the boundary region formed by the arcade.


In software, deep interlock often occurs in the form of inversion of control, where a function calls back into code owned by its caller:
const evens = [1, 2, 3].map((n) => n * 2);
// evens = [2, 4, 6]
A related concept is ambiguity — specifically ambiguity of structural hierarchy. A simple example can be seen in the herringbone pattern below:

Each herringbone is made of two rows of bricks: one angled one way, and one angled the other way. Each of these angled rows belongs to two herringbones: one pointing left and one pointing right. Because the hierarchy is ambiguous, you can perceive the whole as a tiling of left-facing herringbones, or as a tiling of right-facing herringbones. The interlocking, overlapping centers formed by the herringbones make the whole pattern feel unified.
Ambiguity occurs whenever we have an “intermediate” center — often a datatype or interface — which is the common property of all the centers it connects. The data or interface serves the needs of both its producers and its consumers, and is associated with them both.
In people-space, hierarchical ambiguity shows up in matrix management of cross-functional teams. In matrix management, the person who decides what you work on day-to-day (e.g. the product manager) is generally not the person you report to. The decoupling of people management from product management enables the formation of cross-functional teams — teams that have all the expertise (design, business, engineering) needed to create a successful product.
In all cases, ambiguity creates multiple ways of looking at things: more potential groupings of centers, each of which is a candidate for being strengthened or reconfigured as the whole evolves to solve new problems.
This doesn’t mean it’s impossible to notice or appreciate good software, just that it takes a bit more effort to notice than bad software. E.g. web browsers are very good these days — so good that I bet you rarely think about how much they’re doing for you.
I also do not mean to imply that every program or app is either all-good or all-bad. Obviously some parts and features of a program can be better than others. The problem I see is that we seem just as eager to replace good features with bad ones as vice versa.
That is, we should posit hypotheses that are general, and thus predictive and falsifiable.
The readability difference between the two styles of if statement is insignificant for such a simple example, but for larger chunks of code, I find that the else
helps clarify things.