JavaScript Pyramid of Doom — How To Spot and Fix

Anti-patterns. They are the bane of many developers that’s had the misfortune of meeting one. The pyramid of doom is often one that a lot of new JavaScript developers write. Most of the time, it’s written in innocence with no code janitor to tell them otherwise.

I’ve written pyramids of doom in my early days and I’ve experienced them from others. The anti-pattern begins its seeds as a few levels of functions, loops and if else statements — until the levels turn into an endless maze of curly braces and semi-colons that somehow magically work on condition that no one touches it.

What exactly is a pyramid of doom?

A pyramid of doom is a block of code that is so nested that you give up trying to mentally digest it. It usually comes in the form of a function within a function within a function within a function of some sort. If not, then it’s a loop within a loop within a 3 level nested if statement.

When faced with a pyramid of doom, we often ignore the code and just begin again. However, sometimes that’s not feasible because the entire application is written in this anti-pattern style.

It’s an anti-pattern because there is no pattern. It is simply the transmission of a developer’s train of thought as is without any categorization or organization.

Here’s an example of a multi-level beginning of a potential pyramid of doom:

function login(){ 
   if(user == null){ 
     //some code here
     if(userName != null){ 
       //some code here
          if(passwordMatch == true){ 
             //some code here
             if(returnedval != 'no_match'){    
               //some code here 
                if(returnedval != 'incorrect_password'){ 
                  //some code here
                } else{ 
                  //some code here
                }         
             } else { 
                 //some code here
             }
           } else { 
                //some code here
          }
       } else { 
              //some code here
       }
    }
}

There are other ways to code pyramids of doom such as through nested anonymous functions and callback nesting. In fact, if you nest something enough you’ll be sure to create a pyramid from it.

Here are some signs and symptoms that often lead to pyramids of doom and how to cure them.

Lack of planning

Sometimes, developers hit their favorite code editor and start tapping away. It’s alright. We’ve all done it. We take a quick look at the requirements and if there is none, we make it up as we code.

This results in unplanned functions, loops, and statements that need to be written somewhere. Why not just put it right where you’re coding right now?

As a result, we end up building our application in an ad-hoc manner that results in unnecessary code — sort of like if you were to build a house without a plan and just keep rocking on back to the shop to buy more timber. Next thing you know, your budget is blown because you bought too much of the wrong things and you can’t return it.

Cure: pseudo code out your plan

I have my juniors do this all the time to prevent wasted time trying to unravel the nest they’ve written. They don’t get to code anything unless they show me a plan first — even if it’s scribbled down with pen and paper with cross-outs and coffee stains.

The point of the plan is to help structure your thoughts and ensure that you understand the direction of your code. When you are able to do this, it allows you to pre-plan what kind of functions you’re going to write, how your objects are structured and if your inheritance is logical in terms of classification and flexibility.

Basic syntax knowledge only

Many of us jump right into coding because we’re excited. We’ve just figured out how to do a few things and it works. We end up with a pyramid of doom because we don’t know how else to solve the problem.

In this situation, we don’t recognize our anti-pattern because we don’t know any better. However, you can’t build large and complex applications with just basic functions.

Cure: check out OOP JavaScript, inheritance patterns and promises

Upgrade your skills by learning the higher level coding paradigms like object oriented. Although JavaScript is often presented as a series of functions, they are all objects with rules and inheritance patterns.

Understanding the concept of promises will also help you flatten your chain when it comes to writing callbacks and prevent your code from blimping out if something goes wrong. Throw errors when things go wrong so you know when and where things happened rather than having to sit for hours tracing through your code.

Complicate is smart

When starting out and without much guidance, we often create large and complicated blocks of code. Some people do it because they think that’s how code is supposed to be: complicated.

We get this misconception that the harder the code is to understand, the smarter we are for creating such a beast. But that is often the sign of inexperience and hubris.

It doesn’t matter how many months or years you’ve been coding. If your main aim is to make the code as complicated as possible, then it means you’re not versed in programming paradigms. When things get complicated and intertwined, the code becomes much more fragile and prone to breakage. There is no resilience to change and decay at a faster rate.

Cure: Simplify and learn your SOLID principles

Flatten your code and learn to use callback methods instead of nesting functions. Use SOLID principles to guide your choices and the relationships you create.

If you start to see more than one level, you should stop and evaluate your code choices. Most of the time, you can abstract it out — even if you think you’re only going to use it once and never again.

Fix it later mindset

We often tell ourselves that we’ll do it later — but from past experience, never often never materializes. It happens all the time. You promised yourself or get given the promise that you’ll have time at a later date to fix it. But that time never happens. It gets pushed back. It gets re-prioritized. Next thing you know, you’re stuck with a smelly piece of fragile code that you’ve forgotten how it works.

Not only that, you’ve just spent your time further entrenching bad patterns by writing more of the same.

Cure: do it now

It might take more time initially but once you get the hang of how to write flat and clean code, you get better at it. Every time you refactor your own code as you’re working on it, the better you become at detecting smelly code and anti-patterns as you write them.

It helps you build the muscle memory. Even if no one will ever see your code, it is best to keep applying SOLID principles, cohesive design and flat levels. Good patterns are as much a habit as anti-patterns. Name your constants. Abstract your SQL commands. Keep your scopes simple and contained. Avoid anonymous functions as callbacks.

Love ’em globals

Global variables are easy to create and deal with when you’ve got nested code. But bad things happen when you litter your code with them. It might feel safe to do so when your application is small. However, as the code base grows and multiple people start working on it, things can get complicated really quickly.

You don’t know what side effects you’ll have if you modify a global. You’ll need to go variable hunting and figure out how it’s going to impact the rest of the application. You don’t know exactly what it’s going to break, how things are going to break and if there’s going to be a cascading effect.

Then there’s your nested pyramid to deal with. If you need to set a global to use inside your function within a function, then you need to stop right there and rethink your game plan.

Cure: use more local variables

When you use more local scopes, your code becomes isolated and less fragile to change. Your logic gets contained and it forces you to rely on the variables that are immediately available within your scope rather than what’s external.

When you’re not relying on global variables, you can pass states and results between different functions through return rather than worry about how your global state is going to impact on other functions.

Having global variables isn’t bad but they’re best kept in the realm of immutables where things aren’t expected to change.

Final words

You have to work hard to get your thinking clean to make it simple. But it’s worth it in the end because once you get there, you can move mountains.

- Steve Jobs

If you find yourself working with a function that feels overly complicated, chances are, it is complicated.

Pyramids of Doom got its name because it only takes one break in the nest to have it collapse into itself. You might be able to put struts and stickers to prevent its downfall but the bigger your pyramid, the bigger the collapse.

Beautiful code is complexity simplified. It takes more effort, thinking, and skills upfront to create something that is easily understood by others. But your investment will pay off in the long run with a much more robust piece of code that ages gracefully.