How to Make Readable Code: Return Early or If Statement?


Early return from a function is a programming practice many developers follow. At the same time, some choose to declare the function logic inside an if statement and keep only a single point of return. What best practice should you follow that enhances the code readability, maintainability, and performance?

To have readable and maintainable code within a function, use multiple early returns instead of having nested if statements. Doing so will reduce the cyclomatic complexity of the code and easily add new changes.

The article describes common patterns for early return and answers two important questions about function returns: is it possible to return from a void function, and if the return ends with an if statement?

Should I return from a function early or use an if statement?

As a general rule, the function should generally have multiple return statements. That’s because saving a single return statement can make code confusing and difficult to read. It can also make it difficult to debug code.

Early returns make code easier to understand and work with.

What is an early return?

A function usually returns or produces the desired output at the end of it after completing all the required processing. Early returning terminates the function as soon as any deviation occurs to the function’s desired output.

You can declare an early return using if statements that check if it violates any condition and declare a return or throw exceptions to terminate the function execution. Consider the following C# code example.

public class PriceCalculator
{
    public double totalPrice = 0.0;

    public void CalculatePrice(int quantity,
                               double princePerUnit)
    {
        if (quantity <= 0)
        {
            return;
        }
        if (princePerUnit < 0)
        {
            return;
        }
        totalPrice = quantity * princePerUnit;
    }
}

The above function returns early at two points and proceeds with the execution only if those two conditions are satisfied. Also, check out the following method that needs a return value that returns at three points.

public String EarlyReturnFunction(String username, int userID)
{
    if (username == null)
        return "No User Name";

    if (userID == 0)
        return "No User ID";

    return "User is valid";
}

Instead of storing the value in a variable at each if statement, the function returns the value as soon as the output is ready. Let’s see why you should prefer this approach.

Why should you consider using early returns?

The alternative method is using an if statement. Let’s consider the following example, which declares a method that first checks if the token is null and then allows you to proceed with the rest of the code.

public static int isValidTokenExist = 0;

public void ValidateToken(String token)
{
    if (token != null)
    {
        //process the token validity
        isValidTokenExist = 1;
        Console.WriteLine("validity" + isValidTokenExist);
    }
    //other processing logic can go here
}

This example is a straightforward way to handle the function logic. As in the above example, every method can start with a single if statement. But when the function becomes more complex, sooner or later, you will need to add more if statements within the top if statement.
This can get messy and makes it difficult to read the code. For example, check the following code, which has more conditions to check inside the first if statement.

public static int isValidTokenExist = 0;

public void ValidateToken(String token,
                            string userName,
                            int userID,
                            bool hasSubscriptions)
{
    if (token != null)
    {
        if (userName != null && userName != "")
        {
            if (userID != 0)
            {
                if (hasSubscriptions)
                {
                    //process the token validity
                    isValidTokenExist = 1;
                    Console.WriteLine("validity  " + isValidTokenExist);
                }
                else
                {
                    isValidTokenExist = 0;
                }
            }
            else
            {
                isValidTokenExist = 0;
            }
        }
        else
        {
            isValidTokenExist = 0;
        }
    }
}

The function now consists of nested if statements. This way of writing code can confuse readers. For example, if the if block becomes bigger than this, it is difficult to locate the corresponding “else” for each “if block.
Also, suppose the function returns a value at the end of it. In that case, there is only one exit point, and future developers must go through the entire function to find the desired result.
In addition, with one big if statement, it will be difficult to do modifications. Thus, the code will be difficult to maintain. Furthermore, this can slow down the code execution because there are many conditional statements to check to arrive at the desired output.
The second point why you should use early returns is that early returns help to throw out the invalid scenarios from the beginning of the function. For example, in the above fist code example, if the quantity or price per unit is a minus value, we should terminate the functions’ execution immediately or use throw statements to throw exceptions for error handling in the following manner.

public void GetTemperature(float? temperature)
{
    if (temperature == null)
    {
        throw (new NullTemperatureExeption("null Temperature"));
    }
    if (temperature == 0)
    {
        throw (new ZeroTemperatureException("Zero Temperature"));
    }
}

Next, let’s see where the single return mantra originated from.

Where did the mantra of single return come from?

The single return statement principle dates back to the era of functional programming using assembly language C, FORTRAN, or COBOL. The rule states that a function should have one return statement and one entry point. One reason to follow this rule is at the beginning of a function or procedure, and it was a common practice to allocate program resources like memory and file handles and release them at the end of it. Thus, an early return was declared, and the program can cause issues from unreleased resources like memory leaks.

Nonetheless, this rule is now not valid for modern programming languages because most support garbage collection without explicitly declaring them in the code, and exception handling allows explicit resource deallocations. Therefore, a single return is not a must in modern code practices. But it is best to use multiple return statements if the code is ready for early exits, as described in this article.

Common patterns for early return

Guard clause – invert if

One common pattern or early returns from functions is a guard clause – Invert if pattern. A guard clause refactoring is a condition within an if statement that should be satisfied to continue the functions’ further execution. If the condition is not satisfied, it returns from the function providing an early exit point.

Implementing a guard clause is not that hard. All you must do is invert the if statement or flip the true conditional expression to false and vice versa and declare a return.

The following is the code that uses the guard clause pattern.

public static int isValidTokenExist = 0;

public void ValidateToken(String token,
                          string userName,
                          int userID,
                          bool hasSubscriptions)
{
    if (token == null) return;
    if (userName == null && userName == "") return;
    if (userID == 0) return;
    if (!hasSubscriptions) return;

    //process the token validity
    isValidTokenExist = 1;
    Console.WriteLine("validity  " + isValidTokenExist);
}

Notice how many code lines were removed and how much you simplified the function body. Now the function contains three guard clauses created by inverting the earlier conditionals of each if statement, which has become more readable and modifiable for anyone who reads or modifies the code.
Furthermore, if no specific processing logic is required for these separate if statements, you can further simplify the code using only one guard statement.

public static int isValidTokenExist = 0;

public void ValidateToken(String token,
                            string userName,
                            int userID,
                            bool hasSubscriptions)
{

    if (token == null || (userName == null && userName == "") || userID == 0 || !hasSubscriptions) return;

    //process the token validity
    isValidTokenExist = 1;
    Console.WriteLine("validity  " + isValidTokenExist);
}

In Visual Studio, it is very easy to invert the existing if and convert it to a guard clause.

invert if
Visual Studio invert if option

Guard clause library – fail fast

Next, you want to further simplify guard clauses and fail fast. In that case, the best approach is leveraging a guard clauses library such as the Guard Clauses Library, which allows you to write guard clauses with minimal effort and lines of code.

A program consists of a class defined below that sets the user name, userID, and other properties for user objects. For example, suppose the user name and ID are two critical parameters.

Then, to successfully execute the code, you must ensure that all the user objects contain both the user name and userID parameters.

public class User
{
    public string UserName { get; private set; }
    public string Password { get; private set; }

    public User(string userName, string passWord)
    {
        UserName = userName;
        Password = passWord;
    }
}

According to these basic requirements, you must ensure that the program will never create an object without both critical parameters. After that, you can do a few things, like using an if else statement to check the conditions you have been using throughout this article or a try-catch block to throw exceptions.

Either way, you will have to write guard clauses which can add more lines to the code whenever you want to check for more conditions. That is when you can use the guard clauses library and prevent writing a large chunk of code. 

Following is how you can use the Guard Clause library to add guard clauses that check if the parameters contain null values. As you can see, you are just required to write only a single line of code instead of if-else statements.

public User(string userName, string passWord)
{
    UserName = Guard.Against.NullOrEmpty(userName, nameof(UserName));
    Password = Guard.Against.NullOrEmpty(passWord, nameof(Password));
}

Replace control flag with break/return

The third pattern for early return is replacing the control flag using the ‘break’ or return that allows an early return from the method. Let’s take a look at the following example.

public bool FindUser(List<string> userList)
{
    bool found = false;
    for (int i = 0; i < userList.Count; i++)
    {
        if (!found)
        {
            if (userList[i] == "Mark")
            {
                Console.WriteLine("user found ");
                found = true;
            }
        }
        Console.WriteLine("user not found ");
    }
    return found;
}

You can replace the if (!found) control statement entirely if you use the break statement in the following way.

public bool FindUser(List<string> userList)
{
    for (int i = 0; i < userList.Count; i++)
    {
        if (userList[i] == "Mark")
        {
            Console.WriteLine("user found ");
            return true;
        }
    }
    return false;
}

Does return break loop? Yes. Return breaks the loop and exits from the entire function. However, the break only exists from the loop and allows further code execution.

Can you return from a void method?

You can return from a void method. A void method is simply a function that does not return any value to the caller. There are many uses of void methods. It is often used to set values to global variables or set up initial configurations of a program.

If you try to return a value from a void method, it will throw an error. However, the ‘return’ statement is completely valid in void methods.

Because return just terminates the function and does not return any value.

Does return end an if statement?

As mentioned in the above explanation, a ‘return’ statement ends not only an if statement but also terminates the entire function’s execution. Thus, if you declare something after the return statement of an if the condition or in the else part, the rest of those codes become unreachable. 

Also, the compiler will complain if you try to use just a ‘return’ statement for a function. For example, refer to the following code.

public double CalculatePrice(int quantity,
                                double princePerUnit)
{
    double totalPrice;

    if (quantity <= 0)
    {
        return 0;
    }
    if (princePerUnit < 0)
    {
        return 0;
    }
    totalPrice = quantity * princePerUnit;
    return totalPrice;
}

Omiting 0 in the first two returns and using only the ‘return’ statements for an early exit as in the above C# code will throw the following error.

Error CS0126: An object of a type convertible to 'double' is required

Thus, if you are returning early from a function, you need always to return the corresponding value. 

Conclusion

In summary, this article explained an early return from a function and why you should use them within your code over if statements by taking code C# code examples. Guard clauses are the ideal way to mitigate nested if statements and simplify the early return.

You can further simplify the code using a guard clause library. This article also explained how you could use a break statement to replace a control flag within a loop.

Finally, you could return from a void method. Return not only ends an if statement but also terminates the execution of the entire function.

Recent Posts