the inverted pyramid of code


Programming, being an exercise in writing as much as anything else, can look for inspiration from other, more established, writing disciplines. One writing technique of interest is called the inverted pyramid. This approach improves text comprehension by starting with the most crucial information first, before diving into the less important details later on. The reader can quickly understand the basic concept, without having to go through the whole text.

In that metaphor, the beginning of the text is the pyramid’s base- it contains the broadest or most crucial information. The reader could potentially go only through that base and still leave with some understanding of the topic. Similarly, the peak of the pyramid represents the end of the text, where the least important information lives. The content becomes more and more “omittiable” as you move in that direction.

“How does this exactly apply to code?”, you may ask at this point. The same principle can be applied to the levels of abstraction: starting with the most abstract definitions first (the base), moving into the details of the algorithm only as needed (the peak).

a poor quality illustration of the inverted pyramid applied to levels of abstraction in code, by the author

Imagine a function that makes a hot cup of tea for you (who hasn’t written tea-making code at least once in their life, amiright?):

fun makeCupOfTea(coldWater: ColdWater, teaLeaves: TeaLeaves): CupOfTea {
    val hotWater = boilWater(coldWater)
    val tea = brewTea(hotWater, teaLeaves)
    return pourTeaIntoTheCup(tea)
}

You could potentially stop reading the code here and have an overview of what the process looks like, without having to understand how to boil the water exactly, or how to pour it into the cup (????!??!?!??!?!)

Alternatively, if you wish to dive deeper into the arcane art of tea making, you could scroll down to the brewTea function and explore the details there (moving from the base to the middle of the pyramid):

fun brewTea(hotWater: HotWater, teaLeaves: TeaLeaves): Tea {
    val teaPotWithLeaves = putLeavesInTeaPot(teaLeaves)
    val teaPotWithWaterAndLeaves = pourHotWaterIntoTeaPot(hotWater, teaPotWithLeaves)
    return steepLeaves(teaPotWithWaterAndLeaves, 3.minutes)
}

This goes on and on as you move down and into the details of the tea-making algorithm.

Now, you might say: “that is a bit of a stupid example, isn’t it?”, and I’d concur. In any case, it should be enough to illustrate the point.

This, of course, only makes sense when combined with keeping the same levels of abstraction together. All the steps in the makeCupOfTea function are (roughly speaking) equally abstract, as are the steps in the brewTea function.

With this approach, when you open a tea-making function, you don’t get overwhelmed with all the nitty-gritty details of the algorithm straight away. You have control over how much detail you are exposed to at all times, zooming in and out as needed.

Leave a Reply

Your email address will not be published. Required fields are marked *