Click here to Skip to main content
15,888,286 members
Articles / Web Development / HTML

Is it Really Better to 'Return an Empty List Instead of null'? / Part 1

Rate me:
Please Sign up or sign in to vote.
4.85/5 (47 votes)
17 Dec 2014LGPL313 min read 416.9K   51   132
This article aims to answer the question: Should we return an empty list or 'null' from functions?

Part I: Introduction

A Popular Advice: Don't Return Null

There is a popular and widely accepted advice in the world of software development that says:

You should always return an empty list instead of null!

Proponents of this rule argue that there are two considerable advantages:

  • You eliminate the risk of a null pointer error (i.e. NullReferenceException in C#, NullPointerException in Java, etc.)

  • You don't have to check for null in client code - your code becomes shorter, more readable and easier to maintain

Some people are very opinionated about the 'return an empty list instead of null' rule, such as demonstrated by the following excerpt of the top rated answer at the Stackoverflow question: Is it better to return null or empty collection?:

Empty collection. Always.

This sucks:

Java
if(myInstance.CollectionProperty != null)
{
   foreach(var item in myInstance.CollectionProperty)
      /* arrgh */
}

It is considered a best practice to NEVER return null when returning a collection or enumerable. ALWAYS return an empty enumerable/collection. It prevents the aforementioned nonsense, and ...

The commenter reveals another benefit:

... it prevents your car getting egged by co-workers and users of your classes.

This advice is also supported by some prominent and influential voices:

  • Book Clean Code; by Robert C. Martin; page 110: Don't Return Null

  • Book Effective Java 2nd edition; by Joshua Bloch; page 201: Item 43: Return empty arrays or collections, not nulls

  • Book Framework Design Guidelines 2nd edition; by Krzysztof Cwalina and Brad Abrams; page 256: DO NOT return null values from collection properties or from methods returning collections. Return an empty collection or an empty array instead.

  • Book Pattern of Enterprise Architecture; by Martin Fowler; page 496 (Special Case): Instead of returning null, or some odd value, return a Special Case that has the same interface as what the caller expects.

Do we have any good reason(s), then, to question this established advice and ask: Is it really better to 'return an empty list instead of null'?

A list An empty list null

If we read through related questions in Stackoverflow and other forums, we can see that not all people agree. There are many different, sometimes truly opposite opinions. For example, the top rated answer in the Stackoverflow question Should functions return null or an empty object? (related to objects in general, not specifically to lists) tells us exactly the opposite:

Returning null is usually the best idea ...

Functions that return lists are omnipresent. They appear often in virtually all applications and all programming languages. The aim of this article series is to have a profound look at this important and recurring topic. Which are the pros and cons of possible approaches? Is there a one-size-fits-all right approach? Are there special cases? Which approach should you apply in your code?

Let's see!

[Note] Note

This is a follow-up article to a previous article with the title Why We Should Love 'null'. While the previous article focused on the general case of returning null from a function, this article is about the more special case of functions that return a list. In this article, I am going to refer sometimes to the Why We Should Love 'null' article, so you might want to read that article first (or at least the last short section called 'Final conclusion').

[Note] Note

In this article, I am going to use the term list. But the discussion encompasses all types of iteratables, enumerations and collections such as sets, lists, maps, dictionaries, arrays, strings, etc.

A Simple Example

Before analysing different cases, let us first look at a simple example of code that illustrates the rationale behind the advice 'return an empty list and not null'.

[Note] Note
In this article, I use Java in the source code examples. However, you can try out these simple examples in any other language you prefer. The principles we are going to see apply to all popular programming languages.
[Note] Note
Experienced programmers well acquainted with the subject can safely skip this example and jump to the next section.

Here is a method that returns a list of digits found in a string:

Java
public static List<Character> getDigitsInString ( String string ) {

   // 'null' as input is not allowed!
   if ( string == null ) throw new IllegalArgumentException ( "Input cannot be null." );

   List<Character> result = new ArrayList<>();

   for ( char ch : string.toCharArray() ) {
      if ( Character.isDigit ( ch ) ) {
         result.add ( ch );
      }
   }

   return result;
}

As we can see, the above method follows the advice to never return null. If no digit is found in the input string, an empty list is returned.

To use this method, we would write client code like this:

Java
String s = "asd123";
List<Character> digits = getDigitsInString ( s );
System.out.println ( "Number of digits found in '" + s + "': " + digits.size() );

Executing the above statements displays:

Number of digits found in 'asd123': 3
[Note] Note

If you want to try out the above example without using an IDE, then proceed as follows:

  • Ensure first that Java is properly installed on your system.

  • Create file TestList.java in any directory with the following content:

    Java
    import java.util.*;
    
    public class TestList {
    
       public static List<Character> getDigitsInString ( String string ) {
    
          // 'null' as input is not allowed!
          if ( string == null ) throw new IllegalArgumentException ( "Input cannot be null." );
    
          List<Character> result = new ArrayList<>();
    
          for ( char ch : string.toCharArray() ) {
             if ( Character.isDigit ( ch ) ) {
                result.add ( ch );
             }
          }
    
          return result;
       }
    
       public static void main ( String[] i_arguments ) {
    
          String s = "asd123";
          List<Character> digits = getDigitsInString ( s );
          System.out.println ( "Number of digits found in '" + s + "': " + digits.size() );
       }
    }
  • Open an OS terminal in the directory of file TestList.java and compile the file by typing:

    Java
    javac TestList.java
  • Run the file by typing:

    Java
    java TestList
[Note] Note

Each time the above method is called and returns with an empty list, a new empty list is created. To avoid this, we should return a shared and immutable empty list instead. This can be done with Collections.emptyList(). Moreover, it would be better to always return an immutable list. In a real world application, we would therefore replace ...

Java
return result;

... with:

Java
if ( result.isEmpty() ) {
   return Collections.<Character>emptyList();
} else {
   return Collections.unmodifiableList ( result );
}

To understand the rationale of the "don't return null" advice, let's change method getDigitsInString so that it returns null if there are no digits in the input string:

Java
public static List<Character> getDigitsInString ( String string ) {

   // 'null' as input is not allowed!
   if ( string == null ) throw new IllegalArgumentException ( "Input cannot be null." );

   List<Character> result = new ArrayList<>();

   for ( char ch : string.toCharArray() ) {
      if ( Character.isDigit ( ch ) ) {
         result.add ( ch );
      }
   }

   if ( result.isEmpty() ) {
      return null;
   } else {
      return result;
   }
}

Now, executing the same above test code still returns the correct result:

Number of digits found in 'asd123': 3

But what happens if the input string doesn't contain a digit, as in the code below:

Java
String s = "asd";
List<Character> digits = getDigitsInString ( s );
System.out.println ( "Number of digits found in '" + s + "': " + digits.size() );

A NullPointerException occurs, caused by digits.size(), because digits is null.

To avoid this, we have to check for null and our code becomes:

Java
String s = "asd";
List<Character> digits = getDigitsInString ( s );
if ( digits != null ) {
   System.out.println ( "Number of digits found in '" + s + "': " + digits.size() );
} else {
   System.out.println ( "Number of digits found in '" + s + "': 0" );
}

We can improve a little bit:

Java
String s = "asd";
List<Character> digits = getDigitsInString ( s );
int num_digits = digits != null ? digits.size() : 0;
System.out.println ( "Number of digits found in '" + s + "': " + num_digits );

When comparing both solutions (returning an empty list vs returning null), it becomes clear why many developers agree with the "return an empty list" advice. The code of the first solution is shorter, produces less aches in the typing fingers, and does what it is supposed to do without the risk of throwing the infamous NullPointerException.

So ...

What's the Problem?

Before looking at some serious flaws and pitfalls of the 'return an empty list' approach, we first need to have a look at a more general problem that plagues many programmers: the problem of standardization and reliable documentation of APIs that return lists.

To illustrate the problem, let's look at the following method signature of class java.io.File:

Java
public File[] listFiles()

This method can be used to get the list of files contained in a directory. For example, to list the files in C:\Temp\, we could write:

Java
File directory = new File ( "C:\\Temp\\" );
File[] files = directory.listFiles();
for ( File file : files ) {
   System.out.println ( file );
}

An important question arises immediately: What does this method return if the directory is empty? Does it return an empty array or null?

By just looking at the method signature, we can't know. In the world of Java (and also in many other environments), there is no standard rule applied - such as 'always return an empty list' or 'always return null'. In some environments, there might be a general recommendation or a project guideline that should be applied by all programmers, but such a rule can't be checked and enforced by the language or compiler - a programmer could accidentally violate the rule. For example, Microsoft officially recommends in its Guidelines for Collections to 'NOT return null values from methods returning collections'. But this doesn't mean we can count on it. A commenter to the Stackoverflow question Best explanation for languages without null puts it like this (see second top rated answer):

... every time I access a reference type variable in .NET, I have to consider that it might be null.

Often, it will never actually be null, because the programmer structures the code so that it can never happen. But the compiler can't verify that, and every single time you see it, you have to ask yourself "can this be null? Do I need to check for null here?"

Ideally, in the many cases where null doesn't make sense, it shouldn't be allowed.

That's tricky to achieve in .NET, where nearly everything can be null. You have to rely on the author of the code you're calling to be 100% disciplined and consistent and have clearly documented what can and cannot be null, or you have to be paranoid and check everything.

Hence, to know what listFiles() returns if the directory is empty, we can:

  • look at the documentation (if it exists and is up to date)

  • write a test routine to find out which value is returned for empty directories

  • look inside the source code of the Java Development Kit (JDK) (i.e. file src.zip in the JDK's root directory).

In this case of a standard Java class, we are lucky. The documentation exists. This is the answer, retrieved from the Java 8 online API documentation:

Returns: An array of abstract pathnames denoting the files and directories in the directory denoted by this abstract pathname. The array will be empty if the directory is empty. Returns null if this abstract pathname does not denote a directory, or if an I/O error occurs.

Throws: SecurityException - If a security manager exists and its SecurityManager.checkRead(String) method denies read access to the directory

Ok, now we know we have to test for an empty array and for null, for example:

Java
File directory = new File ( "C:\\Temp\\" );
File[] files = directory.listFiles();
boolean files_found = files != null && files.length > 0;
[Note] Note
To keep this example code simple, I didn't write a try / catch statement to handle SecurityException and I didn't (and couldn't!) consider the weird fact that in case of an I/O error, the method returns null instead of throwing an exception.

Sometimes we are not so lucky. There is no documentation telling us if an empty list or null is returned. And we don't have time to write a test routine for every function that returns a list or to study the method's implementation (if we have access to the source code).

In those cases, the best we can do is to check for an empty list and for null, as we did in the example above. But this is annoying (to put it mildly) and reduces execution speed because either of the two tests is unnecessary if the function never returns null or never returns an empty list.

Anyway, this is what we should do. But what happens in practice? Some programmers actually check for an empty list and for null. Some check for an empty list only. Some check for null only. And some don't check anything.

It really gets nasty! (But please stay with us. We will find an easy solution at the end. Promised!)

Suppose that a function can potentially return an empty list or null or - why not - sometimes an empty list and sometimes null, depending on the system's state. Suppose also that nothing in the programming environment prevents programmers from doing the right or the wrong thing. Then we end up with an impressive set of possible combinations. An interesting question arises: What will be the outcome for each case if the function returns with 'no data' and we are actually using the result, for example to get the number of elements in the list.

The outcomes are shown in the table below.

The following pictures (by Visualpharm; licensed under CC BY-ND) are used to denote the outcome for each case:

: Ok; correct execution

: A null pointer error occurs

: The outcome is undefined

Table 1. Different outcomes of handling a function's return value

What the client code does
to check for 'no data'
Outcome if function returns
null empty list
nothing
if ( list.isEmpty() )
if ( list == null )
if ( list.isEmpty() || list == null )
if ( list == null || list.isEmpty() )

Here is an example of how to interpret the table: If a function returns null (1st column), but we just check for an empty list (2nd row), then a null pointer error will occur.

[Note] Note

To be more complete, we could add one more column and two more rows, to end up with a total of not less than 21 different cases!

We could add another column for the case of a function that throws an exception to signal 'no data'.

Moreover, we could add two more rows for the case of using the inclusive or operator (|) instead of the logical or operator (||). They both exist in C#, Java and other programming languages, but they are rarely used in practice.

However, the table above covers already all cases that are relevant to our discussion.

There are some interesting points to assimilate from the above table:

  • The only approach that works in all cases is to check for an empty list and for null. But this approach has a number of serious inconveniences: There is a time penalty. It is cumbersome to write. We have to be careful to first check for null and then for isEmpty(). And we have to use the right or operator (i.e. || and not |). We can alleviate this pain by using an existing or a self-made utility function that does the check. For example, to check a string, C# has String.IsNullOrEmpty() and in Java, we could use com.google.common.base.Strings.isNullOrEmpty() (from Google Guava) or org.apache.commons.lang3.StringUtils.isEmpty() (from Apache Commons).

  • If we lived in an ideal world of consistency where

    or

    then the code would be correct.

    But we don't live in a perfect word. Moreover, writing correct code (i.e. no bugs) doesn't necessarily mean writing the best code, because other factors such as performance, memory usage and maintainability are also important.

    • all functions return an empty list and all client code checks for an empty list

    • all functions return null and all client code checks for null

  • The most relevant point, however, is this:

    In this context 'undefined' means that the outcome is unpredictable and can vary from totally harmless to extremely harmful. We will look at examples later.

    This is a crucial point!

    We all want to write high-quality and bug-free code in the shortest time possible. So what we have to ask now is this:

    Which approach is generally better? An approach that leads to a null pointer error in case of a bug or an approach that leads to an undefined outcome?

    This decisive question is the subject of the next part in this article series. We will compare the two approaches by looking at some typical source code examples and we will consider software reliability, time and space requirements, as well as API differences. Then, we will look at empty lists in real life. Do they exist? How are they used? The outcome might surprise you.

    • If the function returns null and the client code does the wrong thing, then the outcome is always a null pointer error (see first column).

    • If the function returns an empty list and the client code does the wrong thing, then the outcome is always undefined (see second column).

Links to Related Articles

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)


Written By
Software Developer
Luxembourg Luxembourg
Curious programmer. Zealous simplifier. Lifelong student of life.
I care about creating software that is easy to use, reliable, and maintainable.
https://www.cnpp.dev

Comments and Discussions

 
GeneralRe: ALWAYS Null Check Object References Pin
PIEBALDconsult4-Nov-14 5:50
mvePIEBALDconsult4-Nov-14 5:50 
GeneralRe: ALWAYS Null Check Object References Pin
Kevin Marois4-Nov-14 5:50
professionalKevin Marois4-Nov-14 5:50 
GeneralRe: ALWAYS Null Check Object References Pin
PIEBALDconsult4-Nov-14 5:58
mvePIEBALDconsult4-Nov-14 5:58 
GeneralRe: ALWAYS Null Check Object References Pin
Kevin Marois4-Nov-14 6:00
professionalKevin Marois4-Nov-14 6:00 
AnswerRe: ALWAYS Null Check Object References Pin
Peter Josefsson Sweden5-Nov-14 2:47
Peter Josefsson Sweden5-Nov-14 2:47 
QuestionUndefined is... undefined. Pin
Enigmaticatious2-Nov-14 19:36
Enigmaticatious2-Nov-14 19:36 
AnswerRe: Undefined is... undefined. Pin
ChristianNeumanns3-Nov-14 18:52
mvaChristianNeumanns3-Nov-14 18:52 
GeneralRe: Undefined is... undefined. Pin
Enigmaticatious3-Nov-14 19:27
Enigmaticatious3-Nov-14 19:27 
ChristianNeumanns wrote:
If you carefully re-read your comment (i.e. my quoted text) you might discover that the answer is there already.


If you read my response you would have seen that I was talking specifically about your non-defined definition of undefined.

ChristianNeumanns wrote:
That's exactly one of the points of the article: Doing nothing and just continuing program execution leads to unpredictable outcomes that can vary from totally harmless to extremely harmful.


And I completely disagree with that. The only way that the continuing execution of the program can be "extremely harmful" is if the developer purposely made it extremely harmful. There is absolutely no situation under which "normal" execution of code would be harmful.

As I said (which clearly you ignored, or it didn't fit your point?), the most likely outcome is to then attempt to iterate over the results, which if you return an empty list would do NOTHING.

How about you give an example of something "extremely harmful". I assume it would be easy to do so seeing as you are well versed in it being a highly likely outcome?

There is even no point in trying to explain when your "concrete example" starts with the comment "Is there no orders or is something wrong with my computer". Returning a NULL instead of an empty list would get you EXACTLY the same outcome. If there was a problem with your computer it would raise an exception, after all... problems with your computer are "exceptional". Now if you were to return a NULL in the even there is a problem instead of an empty list (which I believe is the point you are trying to make), then again you have bad programming that has allowed the code to return a NOT APPLICABLE value and yet not bothered to tell you WHY it is absolutely nothing. You still don't know which it was, because clearly the method call is flawed if it neither raises an exception nor specifically tells you that in the event of something going wrong it will return null instead of an empty list which tells you there are actually no records. And especially when you make the point of saying that you cannot trust what comes from a 3rd party as to which approach they used, you would be even more confused. The answer however is NOT in determining whether to use empty lists or to use nulls... it comes in bad programming and bad programmers.

If you don't know EXACTLY what that piece of code does in all cases, I would say your unit tests are a failure and you shouldn't be programming if you cannot trust the code you use.

Exceptions get raised when something goes wrong, the signature clearly states it will return a list. If there are no records you will get an empty list, if there is a problem you will get a very well worded exception explaining what it is.

Why is that such a hard concept to grasp?

ChristianNeumanns wrote:
So: If a method says that it returns a date, then it should return a date, right?

What should method getFirstDeliveryDateOfProduct return, then, if the product hasn't been delivered yet?

'null' has been invented to cover these cases, and it should be used for collections as well - for the reasons explained in the article series (especially part 2[^].


Are you serious???

It returns a
C#
DateTime?


Again, the signature CLEARLY explains the expected results.

Again, you are confusing the fact that a list is a type of objects and objects can be null with the fact that a method returning a list has been badly programmed because someone people don't get this.

Again, I say that trying to "examine each approach and compare" when the situation only arises due to bad programming is making the situation worse because you are falsely giving legitimacy to bad programming.

By your own statement "If everything always returned a list we would be alright, or if everything always returned a null we would be alright" is incorrect. If we always returned null we would forever be forced to do additional code to first check that it was null before using it. The current best practice is the most efficient code using the ACTUAL signature that is clearly defined in its syntax.


ChristianNeumanns wrote:
I truly like well mannered and respectful people.


And I like being efficient and to the point... being overly "well mannered" simply for the sake of making someone else feel warm and fuzzy about themselves.

I also like articles which are objectively informative and not purposely slanted towards a specific outcome that just happens to agree with the author... which in all respect and the most sincerest earnest hopes of mutually beneficial productivity... is plain wrong. (see how being well mannered just adds more unnecessary words that don't change the end result?)

I would much prefer a blunt and brazen approach that was always on the money and correct over the most well mannered and respectful idiot (and I am honestly not calling you an idiot, I dont think you are... just somehow misguided with article writing over validity of content). I would take honesty over fake manners any day of the week... I believe it is respectful to be honest, not to be nice just for the sake of being nice.

ChristianNeumanns wrote:

We are all here to learn and exchange and discuss ideas. It is perfectly ok to argue and disagree - if there is a good reason to do so and if we explain why we believe that X is better than Y. But all communication should be made in a friendly and respectful way. Harsh words are counterproductive and reduce karma.


I believe I have a good reason to disagree, I showed points, I proved you incorrect, I disproved counter point, I ignored your waffle about being sick (has no relation at all to the question and only attempts to deviate from the issue, this is programming, it isn't complex human biomechanical functionality in a homogenous environment), and I think as well mannered and respectful as your arguments are... they are MORE counter productive because of how flawed they are.

People come looking for answers and they have been given a best practice which you are now attempting to bring down for what reason? Because there is a legitimate reason to question it? Or just so you can write a half dozen articles for their own sake?

Any harshness of my words will be gone tomorrow... but this monstrosity of bad programming will still be here next week unfortunately and sadly some people will read it and not understanding how it is slanted towards your own point of view may regrettably take it on board.

ChristianNeumanns wrote:
Thank you.

Now everybody knows how brilliant you are.


Nope not me... established wisdom called a "best practice". Was there for a reason, stood the test of time for a reason, and has continued to stand that same test. It should be obvious... but clearly it isn't.

I do believe what you have written is bad programming, I state my reasons are that you are ignoring obvious constructs, superimposing object nullability onto lists invalidly, ignore struct signature definitions, unable to see a simple nullable data type solution, validating bad programming by presuming others have written it in a poor way and then made it sound legitimate to compare the two approaches as if they are somehow equal to begin with when they are not.

I say shame on you because I think this does a disservice to people trying to learn and it is my humble opinion you are doing it to write articles instead of helping people... but that is an opinion and it could be wrong, but I respect you enough to be honest to you. I believe you will be better served by truth than by pretty placation.

An old Taoist saying goes "That which is beautiful is not always true, and that which is true is not always beautiful"

Very fitting
GeneralRe: Undefined is... undefined. Pin
Peter Josefsson Sweden5-Nov-14 3:09
Peter Josefsson Sweden5-Nov-14 3:09 
GeneralRe: Undefined is... undefined. Pin
Enigmaticatious5-Nov-14 9:11
Enigmaticatious5-Nov-14 9:11 
GeneralRe: Undefined is... undefined. Pin
Peter Josefsson Sweden5-Nov-14 9:30
Peter Josefsson Sweden5-Nov-14 9:30 
GeneralRe: Undefined is... undefined. Pin
Enigmaticatious5-Nov-14 11:02
Enigmaticatious5-Nov-14 11:02 
GeneralRe: Undefined is... undefined. Pin
Peter Josefsson Sweden5-Nov-14 11:28
Peter Josefsson Sweden5-Nov-14 11:28 
GeneralRe: Undefined is... undefined. Pin
Enigmaticatious5-Nov-14 12:04
Enigmaticatious5-Nov-14 12:04 
GeneralRe: Undefined is... undefined. Pin
Enigmaticatious5-Nov-14 13:57
Enigmaticatious5-Nov-14 13:57 
GeneralRe: Undefined is... undefined. Pin
Peter Josefsson Sweden6-Nov-14 7:30
Peter Josefsson Sweden6-Nov-14 7:30 
GeneralRe: Undefined is... undefined. Pin
Enigmaticatious6-Nov-14 11:02
Enigmaticatious6-Nov-14 11:02 
GeneralRe: Undefined is... undefined. Pin
Peter Josefsson Sweden6-Nov-14 13:28
Peter Josefsson Sweden6-Nov-14 13:28 
GeneralRe: Undefined is... undefined. Pin
Enigmaticatious6-Nov-14 14:00
Enigmaticatious6-Nov-14 14:00 
GeneralRe: Undefined is... undefined. Pin
Peter Josefsson Sweden7-Nov-14 4:50
Peter Josefsson Sweden7-Nov-14 4:50 
GeneralRe: Undefined is... undefined. Pin
Peter Josefsson Sweden6-Nov-14 7:39
Peter Josefsson Sweden6-Nov-14 7:39 
GeneralRe: Undefined is... undefined. Pin
Enigmaticatious6-Nov-14 11:07
Enigmaticatious6-Nov-14 11:07 
GeneralRe: Undefined is... undefined. Pin
Peter Josefsson Sweden6-Nov-14 13:59
Peter Josefsson Sweden6-Nov-14 13:59 
GeneralRe: Undefined is... undefined. Pin
Enigmaticatious6-Nov-14 14:18
Enigmaticatious6-Nov-14 14:18 
GeneralRe: Undefined is... undefined. Pin
Peter Josefsson Sweden6-Nov-14 14:32
Peter Josefsson Sweden6-Nov-14 14:32 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.