Notes on Fred Brooks's 'No Silver Bullet'

I had a simple problem. An OCR engine found text anywhere on a PDF page. A separate parser could tell me whether a rectangle on that page contained text, an image, both, or neither. Given a piece of OCR output, I needed to decide whether it came from a text layer or from pixels that merely looked like text. When neither answer was reliable, the system would say “unknown”.

The problem was easy to state, easy to understand and easy to test. So why did it take me hours?

At first, I could not decide where in the module the new code belonged. This was absurd. The module was only about 800 lines of Python, all written by me, and not long ago. The insertion point was not mysterious either: after OCR finishes on each page. That narrowed it down enough.

I went there, added a comment: “Update the dataframe with the best guess of where each row came from: text, image or unknown.” Copilot suggested about fifteen lines which did the job reasonably well. I accepted them, moved them into a function, ran the tests and prepared to commit.

Then I looked at the diff. The new function resembled several others. The module was making repeated passes over the same data, calling parsers with slightly different interfaces and extracting one kind of information at a time. It was wasteful and inelegant.

I could have committed the feature as it was. Instead, I started refactoring. I changed the type hints, docstrings, return values and arguments. Then I began deleting code. Eventually, three similar parser functions became one flexible function which did everything I needed. I pushed the commit just in time for a demo. The client was happy.

But they would have been just as happy if I had not spent time on the refactor.

At the time, I believed I had made the responsible choice. I could either refactor now, or save time today and repay it later while debugging under pressure. The second option seemed like living on borrowed time. That is, after all, why technical debt is called so.


The first time I read No Silver Bullet was over a decade ago, when I was a naive, idealistic developer. I had the rare fortune of working under good programming mentors, on a product that was already well established in the market. Everything I did belonged neatly on an efficient assembly line. It was easy to accept Brooks’s argument as gospel.

Over the years, however, I moved between startups, consulting engagements and products—many of them highly chaotic. I kept returning to No Silver Bullet. Now, I think, it is time to assess its thesis more carefully and pragmatically. In doing so, I have also softened my ideas about what constitutes good programming.

What follows is a sequence of annotations and notes on No Silver Bullet. It is not a summary. It is not even complete. It is simply a stake in the ground: something for me to return to a decade from now.

The more things change, the more they stay the same.

Notes From the Essay

Note: I have a paperback copy of the second edition of The Mythical Man-Month: Essays on Software Engineering, Anniversary Edition with Four New Chapters, published by Pearson. The page numbers that follow are from this copy. However, a PDF is available courtesy of Bret Victor.

Brooks’s central thesis is that all software work is cleanly divisible into two disjoint categories (the categories themselves Brooks has borrowed from Aristotle):

  1. the essential: the complex conceptual structures that compose the abstract software entity1 - which have an inherent, irreducible complexity in software which is necessary for the product to do its job; and
  2. the accidental: the representation of these entities into programming languages and their execution with space and time constraints - which are fundamentally a result of less-than-perfect minds and tools.

Right in the abstract, Brooks delivers a blow:

Unless it is more than 9/10 of all effort, shrinking the accidental activities to zero time will not give an order of magnitude improvement. (p180)

On the face of it, it seems like he’s arguing against all that clean coders stand for: modularity, testability and clarity. But a careful reading is a little more nuanced. He’s saying that unless dealing with bad code occupies an overwhelmingly high amount of your time, eliminating it cannot produce an order-of-magnitude gain. I agree with his proposed direction, but not with his magnitude. A blunt reading would mean that I won’t wait until I start investing in refactoring and housekeeping until the effects of bad code start costing me more than four out of five weekdays. I’ll probably start even if it costs me only two days a week. Secondly, he argues for an “order of magnitude” gain. I’ll settle even for a commensurate gain.

His broader point is still valid, though. Eliminating accidental complexity is no silver bullet. The takeaway here is that being eternally bullish on clean code has diminishing returns.

So Brooks’s alternative, clearly, is to attack essential complexity in order to yield an order-of-magnitude improvement, to which, he says,

There is no royal road, but there is a road. (p181)

And that road (at least to my mind, and to my slightly different semantics), passes inevitably through first pruning the accidental complexity before even hoping to eventually penetrate the essence of the system. Drawing analogies from medicine and germ theory, he writes

… progress would be made stepwise… a persistent, unremitting care would have to be paid to a discipline of cleanliness. So it is with software engineering today. (ibid)

What is a “discipline of cleanliness” if not a commitment to clean code?

He then dives deeper into the essential difficulties. This essential complexity, he says, is what is actually hard in writing software: the specification, design and testing of the conceptual construct, not the labour of representing it and testing the fidelity of the representation (p182). This irreducible essence, he proposes, has four properties: complexity, conformity, changeability and invisibility.

On Essential Complexity

When writing about complexity, Brooks throws another curveball:

The complexity of software is an essential property, not an accidental one. Hence descriptions of a software entity that abstract away its complexity often abstract away its essence. (p183)

This seems like a direct challenge to abstraction, a staple of every major programming tradition. So either we have found a contradiction, or we have misunderstood Brooks, abstraction, or both. The problem resolves itself as one reads on. Brooks is not arguing against abstraction itself. His target is an abstraction that goes beyond hiding detail and begins to corrupt the model.

Suppose a function tells you that a piece of text can come only from a text layer or an image, but never from a third source. That is not merely a simplified interface. It is a false description of the domain. Adding the third answer—“unknown”—was not complicating the abstraction. It was repairing it.

He concludes the section by enumerating a bunch of not just technical, but also management problems (all accidental) which complexity begets. Complexity leads to difficulties in communication, and hence to bugs, unmet deadlines and budget overruns. Complexity leads to unreliable programs and unforeseen states. Complexity leads to complicated interfaces which make programs hard to extend without side-effects.

Unfortunately, as stated, we can’t attack complexity itself beyond a point lest we lose the essence of the system. This, perhaps, is what Einstein meant when he said that everything ought to be made as simple as possible, but no simpler.

Brooks goes on to write about the remaining three properties of the irreducible essence. There’s not much I can add to the commentary here, except to note what he says about invisibility: software is extremely hard to visualize. You often end up with

…not one, but several directed graphs superimposed one upon another. These are usually not even planar, much less hierarchical. (p185)

And he further notes that this invisibility “not only impedes the process of design within one mind, it severely hinders communication among minds. (p186)”

On Past Wins Against Accidental Complexity

Brooks then enumerates past breakthroughs that he proposes have solved accidental difficulties: high-level languages, time-sharing and unified programming environments. If the distinction between essential and accidental difficulties is not clear, this section of the essay has many good examples.

…each attacked a different major difficulty in building software, but they have been the accidental, not the essential, difficulties. (p186)

What does a high-level language accomplish? It frees a program from much of its accidental complexity… it eliminates a whole level of complexity that was never inherent in the program at all. (p186)

It’s revealing that Brooks says that something as revolutionary as high-level languages only addressed accidental complexity. If an elitist were to put this bluntly, Python programming or Java programming isn’t a real job. Programming, however, is. This seems like a hard pill to swallow, especially since programmers absolutely love to learn, talk and write about affordances of programming languages. Discourse around programming languages is a worthy endeavour in and of itself, but ultimately, let’s not forget that while all craftsmen nerd-out endlessly about their favourite tools, they know that the tools themselves are not the craft.

Next, Brooks talks about time-sharing, especially as an antidote to batch-processing.

The slow turnaround of batch-programming means that we inevitably forget the minutiae, if not the very thrust, of what we were thinking when we stopped programming and called for compilation and execution. This interruption of consciousness is costly in time, for we must refresh. The most serious effect may well be the decay of grasp of all that is going on in a complex system. (p187)

This I find surprisingly relevant even today, and not remotely for the same reasons as those of Brooks. I think that this phenomenon of batch processing having slow turnarounds and ultimately causing programmers to lose track of their goals is precisely what ails AI and ML programming today. The following scenario is distressingly common: you launch a long running expensive GPU job on a Friday evening, and by your calculations you ought to have a trained model on your hands by Monday morning. But when you return to your desk on Monday, you realize that the job had crashed right as you closed the door behind you on Friday evening.

XKCD: Compiling

In the comic above, replace “code compiling” with “model training”.

A side effect of long running batches is also that AI / ML developers spend far too much time simply staring at loss curves: a sureshot recipe for (sometimes even literal) tunnel vision.

On Potential Silver Bullets

Brooks returns to OOP briefly in the concluding section of the essay. He divides2 OOP into two fundamental ideas:

He notes that both these are abstract, that each may exist without the other, and that each

… removes one more accidental difficulty from the process, allowing the designer to express the essence of his design without having to express large amounts of syntactic material that add no new information content. For both abstract types and hierarchical types, the result is to remove a higher-order sort of accidental difficulty and allow a higher-order expression of design. (p189)

An order-of-magnitude gain can be made by OOP only if the unnecessary underbrush of type specification remaining today in our programming language is itself responsible for nine-tenths of the work involved in designing a program product. I doubt it. (p190)

I’m perhaps not alone in feeling somewhat bleak upon reading this. Many of us would swear by OOP design, and it’s not exactly encouraging to be told that it cannot provide a tenfold gain. But when the prize we seek is a silver bullet, something that promises an order-of-magnitude gain, the cost of the bullet should also be an order-of-magnitude higher. In other words, we swear by OOP because we settle for commensurate, not order-of-magnitue higher, gains.

Here, Brooks finally reaches the section I’d been waiting for: automatic programming as a silver bullet3—the closest analogue in the essay to today’s claims about AI coding agents. Brooks had the bad (or perhaps the good) fortune of dying exactly two weeks before ChatGPT was released. The modern discourse around AI coding agents is so ridiculous that regardless of what anyone might have said about the essence and accidents of computer programming, we today are sure to accept it as the silver bullet. Nevertheless, it’s heartening to read what Brooks said about automatic programming.

The hard thing about building software is deciding what to say, not saying it. No facilitation of expression can give more than marginal gains. (p191)

He then quotes David Parnas,

… automatic programming always has been a euphemism for programming with a higher-level language than was presently available to the programmer (p193).

Those of us who have regularly been using coding agents would understand what Parnas and Brooks both meant. Thinking about code and typing it down is hardly the most expensive part of the job.

On Promising Attacks on the Essence

This is the last section of the essay, in which Brooks enumerates a few methods which, while clearly not silver bullets, can still go a long way towards better formulation. He’s particular about pointing out that none of these are dramatic or spectacular, but are simply steady.

One idea he brings up is continuous refinement of requirements and rapid prototyping.

… it is really impossible for clients, even those working with software engineers, to specify completely, precisely, and correctly the exact requirements of a modern software product before having built and tried some versions of the product they are specifying.

It’s tempting for programmers to imagine their non-technical managers as Dilbert’s pointy-haired boss, and not without reason. But we must pause to ask ourselves if we speak and write so precisely that our specifications might as well be the code.

…one of the most promising of the current technological efforts, and one which attacks the essence, not the accidents, of the software problem, is the development of approaches and tools for rapid prototyping of systems as part of the iterative specification of requirements. (p200)

This is perhaps the best argument I’ve read against premature generalization. My most frequent code review comment is YAGNI, and the replies almost always are “but what if we do need it in the future?” That kind of thinking is precisely what goes against rapid prototyping.

The last strategy Brooks writes about is how and why to cultivate good software designers.

Great designs come from great designers. Software construction is a creative process. Sound methodology can empower and liberate the creative mind; it cannot enflame or inspire the drudge.

Study after study shows that the very best designers produce structures that are faster, smaller, simpler, cleaner and produced with less effort. The differences between the great and the average approach an order of magnitude. (p202)

By the end of the essay, when you look back at all the highlighted parts, it seems like a collection of truisms you’ve always wanted to believe because programmers you respect have held similar opinions; but you did not have (or did not hear or read) compelling arguments for them. That “NSB” builds its case very completely, and ruthlessly, is why the essay has stood the test of time.

Brooks reflected on “NSB” 13 years later in another essay called ‘No Silver Bullet’ Refired. It is no less interesting, and my copy of it is peppered with as many highlights as the original essay. I’m going to leave you only with some parts of the concluding text, which include quotes from R L Glass,

… evolutionary improvements rather than to wait—or hope—for revolutionary ones.

But some of us—those of us crusty enough to think that we are realists—see this as a breath of fresh air. At last we can focus on something a little more viable than pie in the sky. (p226)

This reminds me of the writing of Oliver Burkeman, who often espouses accepting the finitude of life, because it is only this acceptance that can free you from looking for silver bullets.

Let’s start by admitting defeat: none of this is ever going to happen. But you know what? That’s excellent news4.


  1. In the first essay in the book, The Tar Pit, Brooks enumerates and defines various types of programming entities. A program is something that’s complete in itself, meant to be run by the author on the system on which it was developed. A programming product is a program that can be run, tested, repaired and extended by anybody. A programming system is a collection of interacting programs, coordinated in function and disciplined in format—an entire facility for large tasks. Finally, a programming systems product has all of the above properties. (p5-6) ↩︎

  2. Brooks credits this distinction to a suggestion from Mark Sherman of Dartmouth. ↩︎

  3. Although what AI was in Brooks’s day is far removed from our current view of it. ↩︎

  4. Burkeman, Oliver. Four Thousand Weeks: Time Management for Mortals. Penguin Vintage, 2022 ↩︎