Writing/The Unbearable Lightness of Coding
Chapter 1 · Draft · 23 min read
Preface
Python was one of the first languages I learned — and then kept learning, and learning, for many years. Its elegance and versatility kept surprising me, and as I practiced and taught more and more advanced topics, whether design patterns or language internals, it always struck me as the perfect medium to contemplate on, and experiment with, software engineering at large. This book is a culmination of ten years of teaching, in industry and academia — what I hoped to be a comprehensive, practical, and at times philosophical overview of this wonderful language. Somewhat tragically, I finished it right around the time LLMs came out, and now, what with agents doing most of our coding, it lost some of its luster. Still, I think there is value in learning to code — I know it benefits me in my work with AI. It's not so much about knowing the syntax, the libraries or the arcane details anymore (although, arguably, it never was); but its shape of reasoning about problems, which I tried to capture and illustrate here, seems to me like the king's road to building good software. As we collectively reimagine the future of our changing profession, I hope this guide will still come in handy to those of you who want to learn how their ancestors used to type programs, slowly and manually — and the way of thinking that came with it, which informed (and to a large degree, still does) the incredible paradigm shift we live through.
Introduction
A few years ago, I set out to create a university course on software design.
I felt like it was sorely missing from my own education in computer science: I learned all the maths, algorithms and data structures alright — but if I'm honest, it didn't really prepare me for actual work as a software engineer, let alone architect. There was so much I didn't know; so much I had to pick up on the job, on my own, in messy order and under stressful deadlines. Surely, I thought, it would be good to consolidate these various insights into a clear and structured learning experience.
Naturally, the first thing I did was to search for existing literature on the subject, or any other academic programs that would address real software development, architecture or design. To my surprise, there was very little material; and what material there was — like tedious deliberations on the same old design patterns from three decades ago — was hardly relevant to contemporary practices.
One book that did stand out in its utility and originality was A Philosophy of Software Design, by John Ousterhout. In it, he posits a simple and distressing reason for the lack of good texts on the subject: it's all so abstract, so complex, so hard to put into words. I mean, really — how do you teach good design?
Design as Craft
The most common approach is to make lists of examples and rules, "do"s and "don't"s, like Bob Martin in Clean Code. But in my personal experience, such advice does more harm than good: in cases where it's applicable, it's usually quite trivial and straightforward anyway; and in many other cases, where it doesn't actually fit, people still insist on using it — because, well, that's how "clean code" is supposed to look, according to "experts".
You see, resources that dish out bullet points or acronyms of things you should and shouldn't do — while digestible, reassuring, and occasionally useful advice — will not teach you how to write better code, just like reading a law book will not make you a more ethical person. All the rules they describe are fundamentally flawed, because design, like ethics, is inherently contextual: in some cases it makes sense to bend the rules, or break them entirely.
What's missing, then, is a method to recontextualize and renegotiate rules: an explanation of when, where, why and how any given "expert" came up with any given "best practice". This way, the student can determine when, where, why and how to follow it — and when, where, why or how not to. That's what learning design is about; or at least, how I see it — not as memorizing what to do, but in understanding why we do it, and imagining how we can do it differently.
To be fair, in some cases, an "expert" really is an expert, and a "best practice" really is the best way we know of to do something. One algorithm can be unquestionably faster than another — so if performance is key, using the right algorithm is not really a matter of preference, opinion or intuition.
But that's the case in other fields as well, isn't it? From literature, through the visual arts and industrial design, to architecture — they all have the same tension, the same challenge. Any poem you write should be somewhat comprehensible to the reader, so it has to adhere to some grammar or syntax; just like any building you plan, however cool, should also, preferably, not collapse. We can call such considerations the craft of a field — in which case, comparing the performance of different algorithms would be part of the craft of software development.
However, each of these fields also has an art: the difference between what makes a building stand up, and what makes it stand out; the difference between a poem you can pronounce, and one that gives you goosebumps when you do. Great artists are often, if not always, great craftsmen — but they possess something else, something more. It's this elusive insight that is so invaluable to learn, and so difficult to teach.
Design as Art
So how do they teach it in other fields? That was the simple and brilliant question posed by John Ousterhout; and to explore it, he organized a workshop. Seeing how students learn to write prose, or draw, by actually doing it — then presenting it in a group, criticizing each other, revisiting their work and trying again and again — he wanted to see if students can learn to write good code in a similar fashion. His book is a review of common mistakes that were made, scrutinized and corrected during this course; and while it's very enlightening, it's also complementary to the experience itself, by his own admission. The main point was not to teach students what to do and what not to do, but how to think: form an opinion, give and receive criticism, consider their ideas in relation to others, and understand how others perceive their ideas in turn.
And that's cool — it really is; but how does one articulate this fully enough so as to scale or record it? I had close to a hundred students in the audience, so we couldn't afford a discussion as thorough and personal as a small workshop. The crucial conversational dynamic, personal engagement and subsequent growth are hard to recreate in a relatively one-sided broadcast like a lecture — or a book, for that matter. So, what do we do?
Luckily, there is plenty of material to draw on from other disciplines. In fact, Design Patterns: Elements of Reusable Object-Oriented Software, one of the few seminal works on software design that we do have, was inspired by A Pattern Language — a collection of insights in a similar format, written about architecture and urban planning some twenty years prior. That, then, is what I tried to do: apply the advice of designers like Dieter Rams or urbanists like Jane Jacobs — even anthropologists like David Graeber or ecologists like Donella Meadows — to software, to code, to zeros and ones.
In retrospect, I realized this was precisely what my favorite resources on software design did as well, whether books like The Mythical Man Month and Peopleware or blogs like Joel Spolsky's or Paul Graham's. Arguably, it's also the reason I found them so refreshingly illuminating; they, too, were informed (if somewhat tacitly) by other disciplines, from psychology to economics — importing, adapting and synthesizing insights from there.
Incidentally, psychology and economics are about human dynamics, so carrying them over to computer science resulted in acumen not so much about programming as about programmers. Still, what if we were to do more of this? — and do so explicitly and intentionally, looking to apply it to actual code? How would it look?
A Lesson from Marketing
Here's an example. Consider the famous quote by Harvard Business School marketing professor, Theodore Levitt: "people don't want to buy a quarter-inch drill; they want a quarter-inch hole!" Believe it or not, this idea maps perfectly well to something as technical as HTTP authentication in Python. Never mind the details — to send a request with some username and password using the standard library, we'd have to do this:
>>> import urllib.request
>>> password_manager = urllib.request.HTTPPasswordMgrWithDefaultRealm()
>>> password_manager.add_password(None, url, '<username>', '<password>')
>>> auth_handler = urllib.request.HTTPBasicAuthHandler(password_manager)
>>> opener = urllib.request.build_opener(auth_handler)
>>> urllib.request.install_opener(opener)
>>> # and finally:
>>> response = urllib.request.urlopen('<url>')
That is, create a password manager, add our credentials, then create an authentication handler out of it, and an opener director out of that, which we install so it takes effect the next time we "open a URL".
But to paraphrase Levitt: "people don't want a password manager, authentication handler and opener director; they want an HTTP request with a username and password!" — which is, indeed, how the 3rd party requests library handles the same scenario:
>>> import requests
>>> response = requests.get('<url>', auth=('<username>', '<password>'))
That's not to blame urllib, yeah? It makes sense in its own way — and the world is full of code like that, so it's in no way unusually bad (yes, Java, I'm looking at you). While requests achieves the same in a much shorter and more elegant fashion, who's to say the sophisticated manager/handler/opener model is not more useful in other circumstances, where requests falls short?
In other words, the clever design decision wasn't making the authentication scenario simple — any scenario can be made as simple as run_scenario(). No, the clever part was which scenario to make simple; essentially, asking "what do people want?"
And hey — that's the whole premise of the marketing and advertisement industry in a nutshell, so maybe we can learn a thing or two from their experience. Trivial though it may be, at least it prompts us to consider why others might want to use our code, and how they would understand it; which parts we should clarify and simplify, at what cost and to what effect.
A Lesson from Ergonomics
Still, there's a deeper, subtler point. In The Design of Everyday Things, Don Norman correctly points out the flaw in Levitt's logic; it's a witty quip, no doubt, and certainly has some merit — but if we're honest, it's also entirely in line with the quarter-inch deep wisdom of Harvard Business School. If one thinks about it for a moment longer, one is bound to realize that no one — and I do mean not a single person in the entire history of the world — has ever wanted a quarter-inch hole. The root cause analysis — reverse-engineering intent, from "Joe needs a drill" to "Joe needs to drill a hole" — is an excellent start; but it's absolutely bizarre to stop here, unless Joe really is drilling holes for holes' sake (in which case I would argue that what he actually needs is help). More likely, Joe wants to drill a hole for some other purpose, like hanging a bookshelf; or further, storing his books; or further still, reading on occasion.
What, then, if we design a bookshelf that doesn't require hanging, like a book stack? Or a reading chair that can store books, since Joe will presumably need some place to sit as well? Of course, there's a tradeoff with price and space — but then, what about a common, public reading area with Joe's neighbours or colleagues? Alternatively, since bookshelves are not the only thing he'd want to hang, we could design walls that are better equipped to support hanging; or, if it's about books after all, we could store them in a different way altogether, like in digital form on an e-book reader.

With code, the equivalent would be to ask why our request even requires a username and password to begin with. From a customer's perspective, it might be easier to log in with OAuth, where they can reuse their existing social media accounts. From an administrator's point of view, reserved pages with a master password or a secure intranet backend might suffice. And in many cases, with all due respect, data should not be walled off at all — it's kept this way for detrimental reasons, like profit or vanity.
A Few Words on the Economy of Knowledge
This last bit is actually quite important. After all, what's the point of this text? There are many fine programming books out there, and even more blog posts, videos and courses on the internet. But it seems to me that a lot of the high quality content (that is, content explaining not just how things work, but why — or coming along with exercises, for that matter) is not free; and what is free, is often rather basic, or highly specific.
But if you think about it, knowledge should be absolutely and unequivocally free. Not only is it free to share, but it actually grows when shared; it simply doesn't fit an economy of scarce resources. That we live in a society where knowledge is guarded, patented and sold for money is both annoyingly obtuse and incredibly harmful; it shows just how poorly we understand good (in this case, social) design. Instead of learning to collaborate, we focus on how to compete; we try to figure out how to have a good life despite each other, when in fact, everything good in life so obviously comes from and through others. And we share knowledge accordingly: with little trust, little care, and little love.
Unsurprisingly, it doesn't work well. Having taught in the military, the academy and the industry, from the smallest startups to the biggest international corporations, I can assure you first hand that the people with the highest salaries, fanciest diplomas or top clearances are not smarter or better in any way. That they have access to better tools and materials is not some well-deserved reward, but artificial divisions fueled by anxieties and bigotry. In truth, all of it only impedes the sharing of knowledge that could help fix things and make them better.
That is, after all, the ultimate purpose of design: to make things better. But to do so, to make things that are Beautiful and Good — including good, beautiful code — we'd have to discuss such concepts as Beauty and Value; questions of ethics and aesthetics that seem even farther removed from our "rational" computer science. My claim is that they're absolutely essential — and that therein lie answers to many of the problems we face today.
If we're honest, such discussions are shockingly rare in computer science, despite the barrage of new, brilliant discoveries and ideas from contemporary anthropology, critical theory and complexity theory. I could pick any number of strawmen to illustrate my point, but let me choose one whose advice I truly appreciate, and which we'll revisit in depth in a future chapter.
There's an excellent talk by Scott Meyers, a legend in the C++ world and author of such canonical books as Effective C++. In it, he lays out what he calls his most important guideline in design: "make interfaces that are easy to use correctly", he says, "and hard to use incorrectly". The first part is obvious enough, but on the second one he elaborates: any programmers using our code are by and large somewhat intelligent, willing to read some documentation, and sincerely want to solve some problem. So if they have trouble, or fail outright, it's not entirely on them — something in our design must have been off.
This way of thinking and its implications warrant a separate discussion, and have parallels in fields from city planning to psychiatry. But it's also entirely apolitical — I'd even go so far as to call it unethical. It doesn't consider for a moment what purpose the interface serves, and whether it actually should be easy to use correctly, or hard to use incorrectly.
A Lesson from Moral Philosophy
Imagine, for a moment, you're designing a weapon. Making it easier to use correctly would make it deadlier, while making it harder to use incorrectly would let anyone wield it successfully. How crazy would it be if it becomes so simple and lethal that even a child could use it — say, for a mass shooting at their school?
So, to make a weapon "better", we perversely make it more harmful; and to make it "more beautiful", we show Eli Roth mowing Nazis with it, or Cillian Murphy whispering a haunted quote from Bhagavad Gita with great remorse. We normalize and glamorize — but movies, even the good ones, are not real; reality looks and feels quite different. Yes, you might argue, but we need weapons — if only to defend ourselves from worse people; and hey, maybe you're right. On the other hand, this might lead to a never-ending arms race, or other vicious cycles — like when the ensuing devastation pushes people into unprecedented extremes that bring ever greater horrors. But hey, maybe not.
More to the point, what if I asked you to design a weapon so it's specifically better for killing children? At this stage, I hope, deeper morals would kick in: "hold on, what? Children? What is it exactly that we're working on here?" And I know, I know; this is getting really dark, really quick — but cranking up the contrast is a cheap trick to reveal the outlines more clearly.
Most of us, after all, are not designing weapons. But does Facebook's like button, or Twitter's 140-characters limit, or TikTok's AI-generated feed of "if you liked this, you'll love that" — do these things improve the overall quality of online discourse, or reduce it? Are Instagram's infinite scroll or YouTube's autoplay good features, or bad? (Aza Raskin, for one, seems to regret coming up with the former.) And since we seem to care about children, can we honestly say that we did everything in our power to inquire, let alone ameliorate, what our software does to their lives?

Like I said, what really strikes me is that such discussions are almost entirely absent from computer science. What's more, the examples I gave are some of the top products of the biggest tech companies in the world, entertaining billions of users and generating billions of dollars — so we can't pretend it's a minor question, or an understandable lack of resources or expertise.
I can tell it from personal experience, too. A lot of my career centered on cybersecurity; basically, the closest you get to designing weapons as a software engineer. In this field — despite its brilliant thinkers, fascinating sophistication and even occasional lip service to white hat hacking (although we all know cool hackers wear black; just imagine Mr. Robot as a corporate security analyst) — despite all this, nary a word is said about power or violence, trauma or supremacy. Having created several courses on information security (one of them, on EdX, with the highest rating in its category and well over 100,000 students), I'm entirely guilty of this myself. And not to deflect the blame, but thinking back on the people and attitudes involved, I'm quite sure they wouldn't let me write it much differently.
A Lesson from Political Philosophy
You see, computer science has a pretty apolitical culture, and I don't think you can have truly good design, let alone great art, in such an environment. Not because I'm for or against any particular political agenda, but because design and art, at their core, are deeply humanistic endeavors — while an attitude of abstaining from politics is fundamentally inhumane.
And I don't mean you should necessarily join a political party or go out to vote, either; that's electoral bureaucracy, and it has its own problems. What I mean is that we should engage more with society, trying to address its ills and our part in them with all the sincerity, clarity and integrity we can muster — no less in quality than what we would expect from our code, seeing as it's no less important (and arguably, infinitely more so). For all the talk in the tech industry about "making the world a better place", the actual evidence suggests that the effort undertaken by our community to articulate such issues is quite anaemic, if not downright inimical — a hypocrisy our critics and opponents see right through, and leverage to great success.
So what does it even mean, that our field is apolitical? I totally understand anyone who dislikes talking about this dirty business; contemporary politics are very corrupt and ineffective, so in realizing that the game is rigged, people don't really want to play, and I don't blame them. But at the same time, unless you live in complete isolation, everything you do has some impact on society — which makes it political, whether you like it or not; one can't really withdraw from politics, any more than one can withdraw from society. What "being apolitical" amounts to, then, is withdrawing from talking about politics; and that's just a dick move, because our actions and the way we live our life continue to be political — just out of sync with everyone else (and more often than not, to the benefit of those in power).
Carl Jung, one of the fathers of psychoanalysis, had a nice quote to that extent: "in our most private and most subjective lives," he wrote, "we are not only the passive witnesses of our age and its sufferers, but also its makers. We make our own epoch." And for me, at least, there's something exasperating in the appropriation of the slogan "to make the world a better place" by what Bo Burnham calls bug-eyed salamanders from Silicon Valley. This slogan really captures, in essence, what I, for one, would like to do with my life; and I believe it resonates, for the same reason, with many more people — no less aware of its cynical and arrogant application by elites and popular media.
Could we reclaim it? Not necessarily according to my values or yours, but with an acknowledgement that our values will differ, a desire to reconcile them, and resolution in the face of the hard work ahead? Not that such reconciliation is inherently difficult — it's just that the social design we have in place to effect such deliberation and compromise is in very bad shape. Admittedly, such a goal (let alone for a series of programming books) sounds borderline delusional in its grandiosity — so I can only offer it on a "take it or leave it" basis. Like the old Jewish saying goes, "if not me then who, if not now then when".
And One Last Lesson, from History
What I am sure of is that all our progress — and here, AI, robotics and bioengineering come to mind — cannot continue apolitically, undisturbed. The Weimar Republic, for example, had an exceptionally progressive, enlightened and democratic culture, with key figures like Albert Einstein and Walter Gropius, the father of Bauhaus (and an undeniable influence on this very work). Yet within two decades, it evolved into the German Reich — and the Nazis leveraged the fruits of modernity to orchestrate a holocaust unprecedented in its "rationality".
In other words, technological and scientific enlightenment does not, by itself, guarantee moral enlightenment. One could argue, of course, that for all their shortcomings, Nazis did have a knack for good design — at least that's the impression one gets from seeing their brand book, or hearing an occasional reference to the "genius" of Nazi movie directors like Leni Riefenstahl or Nazi architects like Albert Speer.

But in our terminology, what the Nazis managed to maintain was a certain level of craft, not art. For all their "rational" elaborations, their worldview was entirely unsophisticated, sterile to the level of infantilism. Riefenstahl's famous "leader shot from below", or Speer's banal fascination with ancient ruins, or the mystical symbolisms of the SS regalia — none of them were particularly imaginative. If they inspired anything at all, it was that base human tendency for addiction, to seek relief in impossible, dangerous mediums; in this case, utopias. Whether those utopias are fascistic or technocratic (to the extent one ever existed without the other) doesn't really matter; blind faith in anything, whether order or progress, is not well-suited for the diversity of this world. Brought to its logical conclusion, it invariably seeks to simplify, in one way or another — to sanitize, to cleanse, to eradicate. We'll see plenty of similarly blind (if somewhat less insane) utopias along our journey, from Le Corbusier's Radiant City to John Barlow's Cyberspace Manifesto; and we'll see how they always fail, and try to tease out why.
So What is to be Done?
Fuck if I know.
We could start with learning Python, I guess; and I'm only half joking. Needless to say, it's a gigantic project — as I started working on it, it kept going on and on. To align the means with the ends, I thought I'd start rolling it out like this: piecewise, free, and quite unpolished. If at times I get dramatic or morbid, it's from a place of genuine sadness: so much of software (or design, for that matter) is all buzzwords, bullshit and pomp — while so much of the craft is being forgotten, and so much of the art that could have been, hasn't.
This text, then, is not intended to convince, but to inform; it's not a final version, and never will be. You're always encouraged to write to me — contribute ideas and insights, or address faults and blindspots — and I'll do my best to integrate any substantial and constructive criticism into this work. What's more, we'll have established a connection: and in an age of distrust and alienation, this in itself is a radically political act. Doing so not over a shared hobby or common identity, but in a joint effort to understand how we make the world and how we can make it better — that, I would say, is low-key revolutionary.
Still, where to begin? As a programmer, I always have this nagging concern that all this mental masturbation about grand abstractions is effectively an elaborate way to procrastinate. It's like when we endlessly fiddle with our IDE settings, you know? — fine-tuning it to be just right, so when we finally get to write some code, we'll be super-ultra-efficient. Except maybe it was better to just write the code already and move on; I don't know. Anyway, it's a time-honored tradition in computer science: when Donald Knuth sat down to write The Art of Computer Programming, he was upset enough with the limited support for mathematical notation that he set it aside and spent eight years solving the issue to his satisfaction (by developing $\LaTeX$, which basically everyone today uses to write math).
The Roadmap
In this spirit, we'll start with Python, of all things. There's a practical reason for it, too: in order to have a substantial conversation about software design (or any design, for that matter), we have to be able to express and understand nuance; and to do that, we first need to establish a common language. Unfortunately, most of the resources I've seen on software design don't spend nearly enough time developing a shared context — they assume familiarity with one language or another, and plunge right into the exciting patterns and architectures, ignoring the fact that different programmers have different backgrounds and motivations. Talking about something so abstract with this level of misalignment creates a lot of confusion, as different people interpret the nuances, the context, differently. So instead, I'd like to start with something very concrete, and build up from there. We'll encounter software design principles along the way, don't worry; but we'll do so in the context of learning Python. Not because it's necessarily the "right" language to start with, or code in; it's just one of several good options, and it happens to be powerful, flexible and popular enough for our purposes.
Remember, the important part is that we establish a common language — before that, talking about composition vs. inheritance or monolithic APIs vs. microservices (let alone the dialectics of trauma and violence) — all of it ends up too vague to be useful, too detached from reality to capture anything profound about the dynamics or aesthetics of complex systems. It will take some time and effort, no doubt; but slowing down and taking the time to think things through is important. With the rise of websites like StackOverflow and ever more sophisticated and automated IDEs, a lot of software development has become collage-work of copy-pasting: trying to get things done, rather than reasoning about how and why they work — and how and why they could, or should, work differently. This approach, like the political and economic structures that bred it, is fine if all you care about is getting paid (and even then, only in the short-term); but it's not a very good way to cultivate ideas that are original, let alone innovative.
The Unbearable Lightness of Coding
So yeah, in our first volume, we'll talk primarily about Python. I'd like to get everyone up to speed not only on using it, but on understanding and thinking in it. It's not the easiest ramp up for total beginners, and I'll dive in assuming you're somewhat familiar with the language (it does have extensive documentation…) — but I do want to cover all of it, top to bottom.
We'll start from topics as basic as assignment, conditions and loops, and move on through functions and generators, classes and metaclasses, modules and packages. After that, we'll cover cross-cutting concerns like workflow and distribution, testing and documentation, benchmarking and performance, as well as network-, concurrent-, asynchronous- and web-programming, and learn to build basic interfaces, from APIs to CLIs and GUIs. Finally, we'll take a first, direct look at design: the old design patterns and how relevant they still are, and how to manage complexity. We'll encounter a lot of design considerations throughout, but the goal is to establish a baseline for the craft of software development — the unbearable lightness of which is yet to be resolved.

Bauhaus Software Design
The next part, our second volume, is the original seed from which this project sprouted. Armed with our newfound mutual understanding, we'll venture to rediscover, rethink and reimagine software design. We'll go beyond the old design patterns, explore new ones, and develop a more systematic understanding of complexity, as well as a broad set of tools to manage and resolve it.
With this mindset we'll review and elaborate on the most common building blocks of real, full-fledged enterprise systems: data formats, from XML to YAML; storage, both SQL and NoSQL; communication, from IPC to REST to message queues; even orchestration, from VMs to Docker to Kubernetes. We'll see how all these components fit together in various architectures, from monoliths to microservices to serverless cloud; and we'll refine our intuition into a coherent design philosophy — namely, that good design is not about the fashioning of objects, interfaces or brands, but about using such material mediums to fashion humans.