In 2002, Joel Spolsky defined the Law of Leaky Abstractions.

Tweet This All non-trivial abstractions, to some degree, are leaky. –Joel Spolsky

His primary example for a leaky abstraction is TCP. It provides the abstraction of reliable communication. Unfortunately, the layer below, IP, is unreliable. To fix that TCP uses confirmation messages and resends missing packets, which leads to slowness or in Spolsky's words "it leaks".

Another example from Spolsky are uniform memory accesses, which leak in the sense that caches lead to non-uniform performance. Likewise, SQL statements have different performance characteristics even if they look equivalent. Network file systems leak the fact that networks are not as reliable as accesses to a local disc. The C++ string class cannot hide the fact that string literals are char pointers.

Spolsky sees this leakyness as a problem for software development in general. If abstractions were perfect, we could use them, forget about the layers below, and enjoy the simplicity. Since abstractions leak, we must also learn the lower layers. As we build a stack of layers the amount of necessary knowledge grows, although the necessary knowledge for the top layer is ever smaller. The alternative is to accept the increasingly worse performance.

Smart people identified the problem before. For example, Gregor Kiczales describes it in Towards a New Model of Abstraction in Software Engineering (1992):

The "abstractions" we manipulate are not, in point of fact, abstract. They are backed by real pieces of code, running on real machines, consuming real energy, and taking up real space. [...] What is possible is to temporarily set aside concern for some (or even all) of the laws of physics.

Note the "temporarily", which refers to the leakyness. The solution Kiczales proposes is the Metaobject Protocol (MOP), which enriches abstractions with additional means to influence the implementation. He also quotes Wirth from On the Design of Programming Languages (1974):

I found a large number of programs perform poorly because the language's tendency to hide "what is going on" with the misguided intention of "not bothering the programmer with details".

Wirth was talking about designing programming languages and compilers, but it generalizes well to all kinds of interfaces.

Leakyness depends on the User In three cases (TCP, Uniform Memory, and SQL) the leak reveals itself as a performance problem. This means as long as performance is not an issue the abstractions are perfectly fine. There are reasons (usually backwards compatibility) why imperfect abstractions are not fixed. Even if an abstraction is leaky it can still be useful. Sometimes you cannot escape it (uniform memory) and sometimes the workaround is costly to implement (TCP, SQL). So you accept the technical debt for now. Hope the debt does not kill the project. Maybe there will come a time where it is worthwhile to pay off the debt. In the mean time there is no point in sulking in the leakyness of your abstractions. Instead, let us consider a more useful classification for imperfect abstractions.