This post is essentially a write up of a talk my friend Johan Lindstrom did years and years ago, which in turn are ideas stolen from other people. This advice is aimed at really novice programmers who heavily rely only the initial pieces of knowledge they leverage when they start out. I don’t see this advice shared a lot online despite being common knowledge in some circles so please forgive me if you think it’s overly simplified beginner stuff.

IF statements in programming are bad. Johan and I worked on an warehouse backend system. One that involved taking orders, reserving stock, doing stock checks etc. At the time we had two warehouses, DC1 in England and DC2 in America, so code would often look like this (examples are transposed from Perl into Scala):

if (warehouse == DC1)

Our code was absolutely full of these bad boys. Hundreds upon hundreds of separate statements throughout enormous legacy monstrosity. This code base will celebrate it’s 20 year anniversary next year.

def printInvoice(warehouse :String) = {
    val address = if (warehouse == "DC1") "England" else "America"
    val papersize = if (warehouse == "DC2") USLetter else A4
    val invoice = generateInvoice(address, papersize)

Of course, when we added a third warehouse nothing worked and it took an enormous effort to isolate all the behaviour and fix it. Some of the changes were in little blocks that went together. IF <something> assumes a key exists in a map etc or that a function had already been called. Adding the third DC didn’t result in a random blend of features. Just unpredictable crashes and a world of pain.

The way == or != were used would shape the way the default behaviour would play out. Stringification and easy regexs in Perl also made it harder to track down where comparisons or warehouse specific logic even resided.

warehouse.toLowercase == "dc1"    // lowercased alternatives

wh == "DC2"                       // alternative names

warehouse.matches("1")            // regexes are seamless in Perl
                                  they aren't so unnoticeably odd

if (letterSizeIsUSLegal)          // warehouse derived from something
                                  set earlier and not passed through

Perl doesn’t have the support of rich IDEs to help track references and all these different programming styles that have grown over 20 years means the process of finding these errors involves dozens of GREPs, lots of testing and a lot of code base inspection.

It didn’t take too long to realise that our IF statements should be based on small reusable features (ie. modular reusable components) and not switch on a global “whole warehouse” value. This code would have been much easier to manage:

if (warehouseHasConveyorBelts)

if (shipmentRequiresInvoice) {
   val invoice = getInvoiceTemplateForCountry(

Ultimately however, the problem also extends passed this modularity and the realisation that IF statements themselves are bad. Necessary in a few places and possibly the simplest fundamental building blocks of all programs… but still bad… Lets look at a comparison to find out why.

The history of goto

Many languages like C, C++, Java, VB, Perl etc support the GOTO keyword, which is a language construct that allows you to jump around a function by providing a label next to a statement. GOTO will jump to the named label. Here is an example.

#include <stdio.h>

int main(void) {
	int someNumber = 0;
	int stop = 0;
	if (someNumber < 23)

	  printf("hello. app finished with someNumber = %d", someNumber);
	  stop = 1;
	  someNumber += 13;
	  if (stop == 0)
		goto BEGIN;
	return 0;

The code is really difficult to read since execution jumps around all over the place. You may have difficulties even following the simple example above. Tracking the state of variables is really hard. Pretty much everyone is in agreement that GOTO statements are too low level and difficult to use and that IF, FOR/WHILE/DO loops and a good use of function calls actually make GOTOs redundant and bad practice.

Foreach loops are so much more elegant than GOTO statements because it’s obvious that you’re visiting each element once. It really speaks to the intent of the programmer or algorithm. Do-while-loops make it obvious the loop will always execute at least once. Scala supports .map, .filter, .headOption, dropWhile, foldLeft which all perform very simple well defined operations that convey intent to other people reading that GOTO simply cant.

So if a construct like GOTO is confusion, leads to spaghetti code, and can be replaced with more elegant solutions should we not prefer those alternatives? Of course! IF statements scatter your business logic around and leave it in disjointed locations across your code base that are hard to track, follow and change. They make refactoring hard. IF statements are bad for the same reasons that GOTO statements are bad, and that’s why we should aim to use them as little as possible.

Switching it up

Here’s a collection of constructs that can be used instead of IF statements to keep your application more readable, and more easy to follow and maintain.

Switch Statements

Not exactly much of an improvement, especially in most languages, but Scala’s specifically can be. If your choices extend a Sealed Trait, Scala can warn you which switch statements aren’t exhaustive. No DC3 slipping into DC2’s warehouse code paths!

sealed trait Warehouse
case object DC1 extends Warehouse
case object DC2 extends Warehouse
case object DC3 extends Warehouse

val myWarehouse :Warehouse = DC1

myWarehouse match {
   case DC1 => println("europe")
   case DC2 => println("america")

// scala reports: warning: match may not be exhaustive.
// It would fail on the following input: DC3


A super common one, especially for Scala is to map over an optional value only doing something if it exists and doing nothing if it isn’t. This is the functional equivalent of an “if null” check.

invoices.map { invoice => invoice.print() }

Map is way more generic than this. It applies a function to a value inside a Monad and is commonly used to manipulate lists. Please don’t punish my brevity, it’s just an example for my own ends.


Inheritance allows you to override the behaviour of an existing object to do many specific things so it’s absolutely perfect at reducing the use of IF.

trait Warehouse {
  def hasAutomation() :Boolean
  def address() :String
  def isInEurope() :Boolean

class DC1 extends Warehouse {
  override def hasAutomation = true
  override def address = "England"
  override def isInEurope = true

class DC2 extends Warehouse {
  override def hasAutomation = false
  override def address = "America"
  override def isInEurope = false

class DC3 extends Warehouse {
  override def hasAutomation = false
  override def address = "Europe"
  override def isInEurope = true

// App is set up once.
val warehouse = if ("DC1") new DC1 else new DC2.

// use in code
if (warehouse.hasAutomation && warehouse.isInEurope)


When it comes to adding DC3, we have an interface to extend so we know exactly which methods we need to define in order to specify how a warehouse behaves. Our behaviour is vastly centralised. We only have to extend the initial warehouse setup once as well since we’ve bought everything together.

We can also go a step further and make the Warehouse class responsible for doing things. This removes IF statements even more!

object Printer { def print() = ??? }
object Browser { def handle() = ??? }
case class RoutingInstruction(destination :String)
val REDIRECT = 303
type Invoice = String

trait Warehouse {
  def packItem() :Either[String, Boolean]
  def generateInvoice() :List[Invoice]
  def maybeRouteItem() :Option[RoutingInstruction]
  def getNextWebpage() :Option[(Int, String)]

class DC1 extends Warehouse {
  override def packItem() = Right(true)
  override def generateInvoice()  = List.empty // no invoice since we are in england
  override def maybeRouteItem() = Some(RoutingInstruction("PackingArea11")) // we have automation
  override def getNextWebpage() = Some((REDIRECT, "/confirmation/place-on-conveyor"))

val warehouse :Warehouse = new DC1

// look, no if statements yet lots of diverse functionality
// being used.


warehouse.generateInvoice.map { Printer.print }

warehouse.getNextWebpage.map { Browser.handle }

There are some variations of Inheritance I won’t cover, such as Mixins and Traits or Interfaces. They all follow the same theme so I won’t list them individually. The code might be a little crap here because I’m trying to be slightly language independent in my samples.

Function Pointer Tables

You can effectively have cheap object orientation by having a Hash/Map of functions and passing around whole “collections of decisions” together.

def accessGranted() = println("granted!")
def accessDenied() = println("denied!")
val permission = "allowed"

// old, redundant.
if (permission == "allowed") accessGranted() else accessDenied()

// single place for logic.
val mapOfAnswers = Map(
    "allowed" -> accessGranted _,
    "denied" -> accessDenied _

val func = mapOfAnswers(permission) // no if here

func() // executes function which causes println to run

Partial Functions / Closures

Partial functions allow us to build functions using composition which can help mix up and select the appropriate logic without actually having to use IF statements.

def makeAddress(inEurope :Boolean)(country :String)(addressLines :String) =
    println(s"$addressLines\n$country\ninEurope: $inEurope")

val europeanFactory = makeAddress(true) _    // variables type
                                             // refers to a function
val britishFactory = europeanFactory("UK")


Closures are functions that reference variables outside of their direct scope. It allows you to do something like this:

def setTimeout(timeMs :Int, onTimeout :Unit => Unit)

val myVariable = 66
def doingMyThing() = println("myVariable")

setTimeout(500, doingMyThing) // setTimeout doesnt have any logic
                                 but does the right thing

Lambdas are typically short hand syntax for functions so this general class of ideas can be used to encapsulate decision making without callers having to use IF statements everywhere.

Dependency Injection

Dependency injection is generally a technique to remove global variables from an application and is just an application of inheritance to a certain degree but it’s perfect for dynamically changing the behaviour of code without using repetitive IF statements.

// Old code with embedded IF statements

class FetchData {
   def fetchOrders() :List[Order] = {
      if (testMode == true)
        List(sampleOrder1, sampleOrder2)
      elseif (DC == 1)

// New version simply trusts whatever is passed in.

class FetchData(httpLibrary :DCSpecificHttpLibrary, convertor :Option[Order => Order] = None) = {

    def fetchOrders() :List[Order] = {
       val order = httpLibrary.httpGet() // was built knowing which DC
       convertor.map { c => c(order) }.getOrElse(order)

// testing code would make a fake httpLibrary and pass it in before the test. Real code would use the real one.


I’m going to stop list alternatives now but hopefully you go away with some interesting thoughts on the subject and possibly an idea that sometimes IF statements can be detrimental if overused.

Some of my examples are really poor, especially my Inheritance one. I was going to model lots of subprocesses of a warehouse like ScreenFlowChartManager, StockCheckManager and make a warehouse point to them but the code was getting too big for a simple example.

I would accept some criticism that some IF statements can’t be avoided and I would accept that some of these alternatives only move the IF statement to another place in the code base. Certainly dependency injection only moves things to when the application starts. Still armed with this knowledge you can write applications which are easier to maintain and move your variables and mutable state around into places that make it easier to work with.