Understanding the costs of our abstractions

Since the beginning of February, there has been a big conversation about client-side JavaScript in the web development community.

There has been a salvo of blog posts discussing the performance implications of single page applications:

These posts and the resources linked within are worth reading. I think this is an interesting conversation, and I think the ideas extend far beyond front end web development. I spent a lot of time listening to podcasts in the car this past week, and there has been a common thread in many of the discussions that I've listened to: abstractions have (sometimes hidden) costs.

How can we do a better job of choosing the right abstractions as an industry?

I could try to write a bunch about incentive structures, scope creep and rising complexity. But I think most of what I have to say boils down to this: we should aspire to understand the benefits and costs of the abstractions that we build on top of.

Understanding abstractions

Most software projects are built atop a perilously tall stack of abstractions.

Joel Spolsky wrote a post entitled The Law of Leaky Abstractions in 2002 claiming:

All non-trivial abstractions, to some degree, are leaky.

To build context for this claim, Joel uses TCP as an example noting that under certain circumstances, the unreliable nature of the network will leak through the reliability guarantees that TCP provides. Unfortunately, Joel omits an important detail: TCP is not free, it comes with a lot of overhead.

TCP provides a variety of features including packet ordering: for the code that uses TCP, packets appear to arrive in the order they were sent. Many applications don't require all of the features that TCP provides, but many of those applications still use TCP (or even HTTP) under the hood. Check out these resources for more information about the tradeoffs that TCP makes in a couple of different applications:


We can apply the same thought process to React and similar client-side JavaScript abstractions. My oversimplified perspective: React aims to provide a declarative alternative to imperative web APIs for providing real-time interactivity.

The web API provides methods like Element.append() or enables mutation of innerHTML to directly modify the text contents of a document element. React enables programmers to specify the desired state of a document fragment and promises to make the necessary adjustments to the document. In many ways React with JSX imitates the style of many popular backend web frameworks (look at Rails, Django or almost any PHP application). There are several benefits to the declarative interface but I think the most important benefit is composability. Since React components behave like pure functions, they can be reused in myriad ways. But those benefits are not free, React can balloon bundle sizes and increase the time it takes for an application to become interactive.

If your application does not need real-time interactivity, the benefits of declarative components are low or your application does not leverage many of the features that React provides, you should probably seek a simpler alternative.

Eric Bailey describes their experience using a mental health portal that has an endless spinner due to a deadlock in client-side JavaScript. Can most users of a mental health portal wait for the latency of an HTTP request when they interact with the page? Probably. Maybe there is a chat feature in the portal where a client-side component library is valuable; but even still, it doesn't seem like a use case that necessitates React.

Python and high-level scripting languages

There are a few technologists that have derided the overuse of high-level scripting languages like Python and Ruby. Many of these folks come from the game development space where performance is paramount. Two of the loudest voices in the room are Jonathan Blow (see Preventing the Collapse of Civilization) and Casey Muratori (see How fast should an unoptimized terminal run?).

Casey recently started a "Performance-Aware Programming" series. So far, Casey has published a prologue that compares the performance of naiively adding two integers in Python with a variety of implementations in C, ultimately getting close to optimal with the use of SIMD instructions and multi-threading. Casey achieves a staggering ~8,000x speedup over the Python implementation. Casey wraps up the prologue by demonstrating and benchmarking alternative summation implementations in Python.

During these videos, Casey describes the interpreter overhead of Python as "waste". I think this discounts the value that high-level interpreted programming languages provide. As a self-described static typing fanatic, I still find Python to be significantly easier to use for creating rapid prototypes than languages like Rust, C++ or C. For getting up and running, Python and similar languages enable you to focus more on the problem that you're trying to solve and less on the mechanics of the language that you're using to solve that problem. Not to mention, Python has a huge batteries-included standard library and an enormous ecosystem of third-party libraries to lean on.

I believe that scripting languages are a good choice as long as you acknowledge the performance and maintainability tradeoffs that you are making. Often, this explicit acknowledgement or context is missing.

Leaving a paper trail

When we make decisions about the tools and technologies that we use to build applications in a professional setting, we should aim to leave a paper trail. Even if the rationale for using a particular technology is "we needed to get started and this was the most popular option," that is valuable context!

When we have that context, we can make informed decisions about how to move forward, whether that means forging ahead with the existing choices or making adjustments to set ourselves up for the future.

Of course, outside of a professional setting or when writing software for yourself, use whatever you want! If you want to experiment with React, experiment with React. If you want to write C++ in the comfort of your own home, do it! But when writing software professionally or for a larger audience, it's significantly more important to think about the implications of the decisions we make. When that consideration is missing, we're doing a disservice to our colleagues and our users.