Bailout Returning

Often in a function you have to deal with a special case. When I say "special case" I don't mean the sort of special case that's been tacked on later in a functions life ("ohh, foo does almost exactly what I need, if I just add a new parameter I can make it..." ← (never do this)).

In this situation, the special cases I'm talking about are ones that are know at the function's original authoring; eg:

  • the function takes an array which might be empty
  • the function takes a parameter which might be null
  • the function performs an operation unless certain, very specific conditions are met

In these situations, 95% of the body of the function won't do anything, the function should simply return whatever value it's meant to in these special cases.

Solution 1

const foo = input => {
	if (isValid(input)) {
		//do lots of calculation and...
		return something;
	} else {
		return null;
	}
};

Here isValid is our simple check for special cases, if no special case problems are found then we do our calculations, else we return a default value that signals we couldn't do anything. This style of solution has a fair amount to recommend it:

  • We're using happyPath == true, sadPath == false; a useful convention
  • We have a balanced else statement, which can often make code easier to reason about.

However, I prefer a different way of handling these special cases:

Solution 2

const foo = input => {
	if (isInvalid(input)) {
		return null;
	}

	//do lots of calculation and...
	return something;
};

Here, we instead check isInvalid, if there is a special case problem then we return our default value, else we carry on with the function calculations and return.

I prefer this style, as it no longer suggests that this special case handling is part of the core logic of the function. When the special case handling is in an if/else branch it means 95% of the logic of the function is wrapped in a conditional that has no real baring on its functionality. This is makes it harder to reason about the function, and also increases indentation.

This hopefully becomes clearer when you compare a function with a special case check to the following variants:

Compared with no special cases

const foo = input => {
	//if (isInvalid(input)) {
	//return null;
	//}

	//do lots of calculation and...
	return something;
};

Compared with multiple special cases

const foo = input => {
	if (isInvalidReason1(input)) {
		return null;
	}
	if (isInvalidReason2(input)) {
		return null;
	}
	if (isInvalidReason3(input)) {
		return null;
	}

	//do lots of calculation and...
	return something;
};

Use Cases

A very common use case for bailout returning is redux reducers:

const reducer = (state, action) => {
	if (!action.success) {
		return state;
	}

	//do lots of calculation and...
	return newState;
};

or in react components:

const Component = (props) => {
   if(props.error){
      return null
   }

   return <div>
      { /* lots of clever components... */ }
   </div>

Conclusion

Although this seems like a very small and trivial change, it often really helps me to reduce the complexity of a function, reason about it more easily and reduce my cognitive load. Next time you find yourself wrapping most of your function code in a conditional, just to return null, consider Bailout Returning instead.