Skip to content

Minimalism in Programming

February 18, 2018

I’m 32, and I’ve been programming actively for over 16 years at this point. I don’t have a lifetime of experience doing this, but over the years, I’ve come to develop a certain style, a kind of philosophy or methodology that I try to apply in everything I do. Crucially, I would say that I’m a minimalist. I like to build things that are only as complex as they need to be to accomplish their purpose. I like to distill ideas to their simplest form.

Much of what I will discuss in this post may seem like common sense to many of you. I’m probably not the first one to tell you about the principles of KISS and YAGNI. Unfortunately, I think that the art of building simple, robust engineering is something that is rarely taught in universities, poorly understood, and often disregarded. We live in a world full of bug-ridden, poorly written software, with a thousand useless bells an whistles. I don’t believe it has to be this way. In my view, much of the bugs we encounter could be avoided if more programmers followed some basic principles to minimize complexity.

Back when I was a teenager, back in the early 2000s, one of the first programming projects I embarked on was some ambitious 3D game. I recruited several people to work on this project with me. We produced a lot of art, and we did implement a game engine and even a map editor. We had some nice screenshots to show. Unfortunately, no game ever came out of it. One of the main issues is that there was a lack of focus on my part. I wanted to build something more awesome than Unreal, Quake 3 and Half-Life, and I thought I needed killer tech to do this, but I didn’t really have a specific game in mind. I had no specific goal, and so no concrete plan. I would guide the project in whatever direction seemed most interesting at the moment. Every two weeks, I’d start working on some new feature for the game engine, but never quite finish it. We most likely could have built a game, if I’d been willing to aim at a simpler, more realistic objective.

These days, before I even start on a new project, I try to spend some time doing some research to convince myself that this project is worth doing, that I have the time to do it, and that I can set realistic goals. I try to start small. I ask myself what is the smallest, simplest version of my idea that I could implement, with the least amount of features, and I try to plan out the steps I will need to complete to get to that. Simply put, the first step, in my view, is to clearly outline what the Minimum Viable Product (MVP) is going to be. Defining an MVP helps me stay focused, and it also ensures that I have a goal simple enough that I can be sure I’ll stay motivated long enough to get there.

Many people make the mistake of thinking that if they don’t immediately account for all the features they could possibly want to add to a project from the beginning, they might paint themselves into a corner, unable to refactor the code, unable to bring the project where they ultimately want it to be. My counter-argument would be that refactorings are inevitable. You will make design choices that turn out to be wrong. You will need to change your code. You simply can’t account for every possibility and every single interaction from the beginning, because there are too many unknowns. If you start with an MVP, you will gain a lot of insight in the process. You will also have a working product that is very simple, and so very easy to refactor.

Trying to build simple products will help you keep people motivated, gain insights, and ultimately reach your goals. It might also help you avoid bugs. Less code means less corner cases, less things to test, less that can break, and less to debug. This is something that good engineers understand very well. When you minimize the number of moving parts, when you minimize complexity, you minimize the chances that your product, and your project, will fail.

I think most of what I’ve said so far is commonly accepted wisdom among seasoned programmers. What I’d like to bring up next is that minimizing the complexity of your software, eliminating possible points of failure, is not just about simplifying the code you write. In my opinion, it also applies to the things your software stands on. The software you build necessarily makes a number of assumptions, and has external dependencies. Most programmers, it seems to me, follow the “don’t reinvent the wheel” philosophy. If something has already been implemented, you should just use it, never implement your own. This is seen as a way to minimize the complexity of your software. The problem is that not all external dependencies are created equal.

Every library that you import is a piece of software you don’t have control over. It’s something that needs to be built and installed in order for your software to run. It’s a black box with its own many dependencies and possible points of failure. How often have you tried to install a library or piece of software and found that it was broken out of the box? If your software has 15 external dependencies, then quite possibly, over the next year, one of these will break, and your software will be broken along with it. If you’re programming in Python, chances are that your software will break several times over the next few months, as packages are being changed and broken under your feet.

When I write software, I try to minimize the number of dependencies I rely on. I do this both to minimize possible points of failure, and to make sure that people installing my software won’t have a terrible time getting it to work. When I have to rely on external dependencies, I try to pick the ones that are more established and well-maintained rather than obscure ones. Sometimes, I will “reinvent the wheel”, when I judge that the effort required is small enough. Obviously, this doesn’t always make sense. If you roll your own crypto and you’re not a crypto researcher, you deserve to be slapped upside the head. However, if you need to load textures in your indie game, you very well could implement a parser for 24-bit TGA images instead of relying on some library which itself has 50 external dependencies. This can be done in less than 100 lines of code.

There are other ways in which you can minimize points of failure and make your software more reliable. You can prefer simple file formats, prefer open standards, and avoid proprietary solutions. Ultimately, the best way to keep things simple, however, is to simply have less features. I’m a minimalist. I prefer to build software that does one thing and does it well. Ultimately, you’ll never make everyone happy, you’ll never satisfy every use case, not without contorting your design into something unmaintainable and fragile.

30 Comments
  1. Mike S. permalink

    I agree with your points, and came to a similar conclusion about minimizing external dependencies.

    Most of my professional experience is with C++ and Java. In my experience with those languages the biggest reason otherwise bright, experienced software developers make things far more complicated than they need to be is failure to isolate modules. For example, at my current job the code that constructs database queries often passes around instances of the ‘FormInputParams’ class we use to handle HTML form inputs and also a ‘UserPreferencePackage’ to track user settings and even sometimes a list of ‘Presenter’ objects controlling how specific outputs will be rendered. FormInputParams, UserPreferencePackage, and Presenter are all useful, essential components of our application and our query library is useful and essential too. But none of them should reference each other. Instead we have a colossal mess that’s hard to read, hard to refactor, and makes it far more time-consuming than it should be to write unit tests.

    If I was CTO I would be tempted to decree: “Each code module can use any class design you like inside it. But all interaction between modules can only use Strings, Numbers, Booleans, enums, and members of the Java Collections package: Map, List, Set, nested if necessary. You can compensate for your missing type-safety by adding validation methods. The important thing is, the modules must be isolated.”

    Most of the team is in our 40s, and I’m the idiot in the bunch. The rest have M.S. and PhD degrees from schools like Stanford, Carnegie Mellon, and UPenn. Yet somehow this is the mess we’re buried in.

    • Brian permalink

      ‘ Yet somehow this is the mess we’re buried in’
      Reason – Qualifications don’t always equate to commonsense!

      • Mike S. permalink

        I should have left off the credentials. They really are a bright crew, friendly, humble, brilliant. It’s the best place I’ve gotten to work because I like everyone and I learn from everyone.

        But I found this blog because my love for static type systems has been eroding, and Ms. Chevalier expressed some advantages to dynamic typing when it seems like an awful lot of bloggers that touch on the issue have static typing as a religion.

        I think there is innate conflict in general and maybe especially in Java between enforcing clean separation of modules and taking advantage of the benefits of a static type system. The form-handling class and user account settings class get passed around in our code because they already have all the data they need to collect converted to the appropriate types and reachable through convenient accessors. It’s a lot of extra work, and from a type safety perspective counter-intuitive, to throw all of that away to hand validated but otherwise effectively raw data into other modules. So there’s a trade off involved, and our team followed what I would call industry convention among teams using static typing and decided the type safety outweighed modularization. From the benefit of hindsight, I am confident they were wrong – but I doubt more than one or two other people on our team of twelve would agree.

        I’d say it’s unfair to label it a common sense problem.

    • Sometimes people just want to get their work done and go home. They want to maintain peace, avoid conflicts with coworkers, and they’re not passionate enough about these issues to stand up to management/teammates and tell them “this is bad, can we please pause and take the time to eliminate this technical debt?”. Realistically, too, if management doesn’t understand the value of simple and clean code, there is really not much point in getting into a fight, you’ll be the one to suffer. These things can be tricky. I think it works best if you start with a small team and instil these values from the start.

  2. Brian permalink

    Minimalism in Programming – definitely makes a lot of sense (will someone please inform sales and marketing!)!

    One reduction in the risk of dependencies is to use ones that are not changing rapidly, and then just don’t update unless absolutely necessary. Easy enough in C++ and even possible with web applications if you have access to the source of dependency and can host it yourself.

    • I’m all for dependencies that don’t change rapidly. What I really dislike is what I would call “change for the sake of change”. Some new version of a library comes out, and the API has multiple breaking changes, seemingly for no reason. It would have been easy to maintain backwards compatibility, but no. It can be good, when things work relatively well, to keep things as stable as possible. If you’re going to redesign your API, maybe you should create a new package.

      OTOH, stable libraries are good, but only if there is still someone maintaining them. As you said, if the libraries are small and open source, however, you do have some hope of fixing them yourself if that happens. Big proprietary messes, if they break, are hard to fix.

      • I feel this way about QT. Most of the core modules haven’t changed or don’t require the latest hardware, yet everyone is dragged along with a version change because no-one at Trolltech implemented module versioning.

  3. lerc0 permalink

    Somewhere (hackers dictionary maybe?) they encapsulate this idea as “Either simple enough that there are obviously no errors, or complex enough that no errors are obvious”

    • Tony Hoare permalink

      Hi, I’m Tony Hoare. You may remember me from such technologies as “Quicksort”, and “The Null Reference”.

      There are two ways of constructing a software design: one way is to make it so simple that there are obviously no deficiencies and the other way is to make it so complicated that there are no obvious deficiencies. The first method is far more difficult…

      • Brian permalink

        “The first method is far more difficult…”
        The second is the most common…. :(

  4. for python a big no

    that’s why you have requirements.txt and virtualenv

    you normally pip install requirements.txt

    inside that nice file you have

    lib==5.4.2
    anotherlib==4.9.5

    i wonder how these can get broken under your feet unless best practises are not being followed. bad example?

    • 1. Having to resort to run every python program in its own virtualenv seems like a pretty awful solution.

      2. lib and anotherlib can still have their dependencies broken.

      • of course when pip installing command line you can specify lib version
        like pip install django==1.0.0
        python won’t upgrade it by itself. no need for virtualenv each time.
        even if you need it’s like git init each time. you will need it for tests

        2. meaning ?

        • Pinning libraries temporarily solve your reproducibility problem but not the dependency problem mentioned in the post. For various reasons you might be forced to update unrelated dependencies (e.g. security issue in X, new feature in Y), which becomes ever more complex, the longer you deferred updating in a fast-moving package ecosystem.
          So pinning dependencies helps you to choose the time for an update, but doesn’t really avoid the effort, and CVEs might crush this deferral plan.

        • no worry but the point was : how is py more prone than other langs

        • Python is more prone to this because it has at least 4 different package managers, two different versions of the Python language itself (which are evolving separately but are somewhat compatible), and, in my opinion, a culture of import-all-the-things and general carelessness.

        • 4 recommended package managers?
          2 versions or one current popular v3 recommended version? v3 3rd party lib support?

          normally for efficient coding, ides are integrated with pep8 detection and if not, plugins are available. if you follow the pep rules no carelessness there are.

          btw the minimalism approach is a nice one. taking time to implement it on : https://github.com/Abdur-rahmaanJ/POSMS

          your article was published at about the same time i was starting it. will drop a note once i release all the documented planning.

          believe me once you start coding in py doing at least a 500+ lines project, you start getting used to doing things the py way.

          coming from java and cpp i was doing cpp or java programming in py but there are times that the code is really “unpythonic”, what happens when py is not taken as a language in itself, a language to be learnt.

          a good ide is the begining.
          pycharm wing else spider via anaconda where you’ll also get the jupyter notebook. these make py programming a real charm

  5. Mr.Magne permalink

    > you very well could implement a parser for 24-bit

    Or use the excellent stb_image.h (https://github.com/nothings/stb/blob/master/stb_image.h), one header file, no dependencies, lots of supported format and permissive license. (check out his other libs, it’s worth it). Otherwise, I agree with your minimalism approach :)

  6. Nice post! I’m also a minimalist. And I agree that, as a practical measure, minimising dependencies has a lot of value.

    However, once we get past Stockholm syndrome effects, it’s a tragedy. Why should building on others’ work so often be so much more effort than it’s worth?

    Research question: how can we make software that doesn’t have to be “maintained”? E.g. how can we allow software to evolve, including in the interfaces it exposes, without causing huge knock-on costs?

    (I have an agenda, in that my PhD work was in this direction. It stopped far short of delivering a usable tool, but I like to think it had some good ideas. Of course, the fact that this is a cold research topic guaranteed that I couldn’t continue it afterwards. I hope to get back to it one day. More generally, there are countless ways in which the experience of developing software doesn’t “have” to be the way it currently is.)

  7. Thanks for the nice article.
    A common variation on this is complexity due to insufficient or shallow understanding of a domain.
    Focusing on only the core features requires a very good understanding on what you actually want to do.
    Technology-centered projects st. ask “what can you do with X” instead.
    If there are uncertainties on both sides, one is easily left with dozens of tools x hundreds of possibilities.

  8. fajarlaksono permalink

    this is good post, I’m looking forward to learning more.

  9. I first have to say, YES, I’ve been programming for 12 years (whereof 5-6 years where I actually can say I mostly know what I’m doing). During those years I’ve been increasingly been understanding the importance of minimalism in coding and now I deem it one of the main qualities to increase the quality of ones code.

    Also I need to thank you for the references that you used in the text, thanks to those I got several additonal references to a text I wrote which might just have been mentioned without a reference otherwise. So thanks for that! ;)

    Best regards,

  10. Bin permalink

    Totally agree with you. I just wrote a 1000 line png parser recently and it took me 20 days. I think it was worth it.

    • Can’t tell if you’re being sarcastic or not, but for simpler formats like BMP and TGA, you can certainly write parser in 100 lines and half a day.

      • I really need png to support images that have alpha channels and some compression needs. Because libpng and stb_image have a lot of hard to understand code in them. So I made my own version.
        I think it’s a kind of minimalism in programming, like you said, “When I write software, I try to minimize the number of dependencies I rely on.”
        Maybe it took a little bit more time, but it’s worth it when that kind of functionality can be used for decades. I’m currently writing my own glTF parsing and truetype support, and after that I plan to write a GUI library like imgui for my game engine.

Trackbacks & Pingbacks

  1. Keep It Simple: How to Become a Minimalist Programmer
  2. They Might Never Tell You It’s Broken | Pointers Gone Wild
  3. The Need for Stable Foundations in Software Development | Pointers Gone Wild
  4. Code that Doesn’t Rot | Pointers Gone Wild
  5. Minimalism in Programming Language Design | Pointers Gone Wild

Leave a comment