Click here to Skip to main content
15,891,867 members
Articles / Programming Languages / Java

Is it Really Better to 'Return an Empty List Instead of null'? - Part 3

Rate me:
Please Sign up or sign in to vote.
4.25/5 (36 votes)
8 Jan 2015LGPL39 min read 76.2K   293   16   79
Part III: Empty Lists in Real Life

Table of Contents

Home

Part III: Empty Lists in Real Life

Introduction

In the previous installment, we saw why it is better to return null instead of an empty list if a function is called with 'no data' to return. For example, the following Java function ...

Java
public static List<ICustomerOrder> getOrdersByCustomer ( String customerID )

... should return null (and not an empty list) if no orders exist for a given customer. As we saw, this reduces the risk of bad outcomes due to a bug and has other advantages.

In this part of the article series, we will look at how we use lists in real life.

When thinking about how to design or implement a piece of software, I find it often useful and illuminating to consider how things work in real life.

So, what can we say about empty lists in real life?

Do they exist?

Do they occur frequently?

Do real life examples confirm our conclusion from the previous installment?

Let's see.

Alice's Shopping List

Imagine the following story of Alice and Bob:

Alice: "Bob, could you please go to the groceries and buy some food?"

Bob: "Yes, of course!"

Alice gives Bob a shopping list.

Bob drives to the groceries. He looks at the list and sees that it is ... empty.

Alice's shopping list

Alice's shopping list

Bob drives back home and gives Alice an empty box.

Bob: "Here is the food you asked me to buy."

Alice: "Thank you darling."

What we perceive as ridiculous, stupid or funny in real life has happened (and will happen) billions of times in the world of software execution. It happens every time (in avoid-null environments) a function returns an empty list (instead of null) to denote 'no data'.

One might argue that handling an empty list in software doesn't consume considerable time and resources - in contrast to Bob who spent time and used his car. It is true that the time and resources needed to handle an empty list is negligible in many cases. Nevertheless, there is a time and space penalty, and the consequences can be dreadful. Just imagine a worst case scenario of a network connection that must be established and data that has to be exchanged between computers to handle an empty list. And then, once in a while, there might be a network connection failure which leads to a total system crash because the operation 'do nothing' could not be executed. This would be similar to Bob having a car engine breakdown (or something worse happening) when he drives to the groceries to 'buy nothing'.

On the other hand, as we saw in the previous installment, null is always cheap in terms of time and space.

Bob's Postage Stamps

Here is another real-life example:

Bob collects postage stamps.

Alice doesn't collect postage stamps.

Bob has a box labelled 'Postage stamps' containing his stamps.

Does this mean that Alice has an empty postage-stamps-box because she doesn't collect postage stamps?

No, of course not! She simply doesn't have a box for stamps.

Just imagine everybody in the world had an empty box for everything he/she doesn't collect. Weird!

So, what should getPostageStamps in the following Java interface return if a person doesn't collect stamps? An empty list or null?

Java
interface IPerson {

   public String getName();
   // ... more attributes

   public List<IPostageStamp> getPostageStamps();
}

Returning an empty list is like Alice having an empty box.

Returning null is like Alice having no box at all.

[Note] Note

In languages that support the Optional/Maybe pattern, an alternative solution would be to always return a non-null Optional or Maybe object. The basic idea of the Optional/Maybe pattern is this:

Instead of providing a non-null value or null, always provide a non-null container object that either contains a value or doesn't contain a value.

Please refer to my previous article Why We Should Love 'null' (chapter 'The Optional/Maybe Pattern') for a discussion of this solution.

Is this pattern popular in real life? I don't think so. Just imagine an application of this pattern in our example. Alice doesn't collect stamps. Therefore she has a box that contains ... nothing. Bob collects postage stamps. So, he has a box that contains a box that contains postage stamps.

Customers in Luxembourg

Suppose we have the following method to get a list of customers by city:

Java
public static List<ICustomer> getCustomersByCity ( String city ) {
   // code to retrieve customers from database and return the result
}

What should this method return if there are no customers in the database for a given city?

If we read through the many forums that discuss this kind of question, we can see that we typically get the following three different answers, sorted by popularity (the first one being the most popular one):

  1. The method should return an empty list
  2. The method should return null
  3. The method should throw an exception

Another important question is this: How should this method behave in case of a resource error such as a database connection error at runtime? Again, depending on who you ask, you'll get different answers.

To find the correct answers, let us think about how such a case would be handled in real life.

Imagine:

Big boss to assistant: "I need a list of all our customers in Luxembourg. Could you do that for me please?"

The assistant obliges. His or her task is to launch a query in the company's ERP software in order to print out a list of customers in Luxembourg.

There are three possible outcomes:

  1. The company has customers in Luxembourg:

    The assistant prints out the list and hands it over to the boss.
  2. The company doesn't have customers in Luxembourg:

    The assistant tells his or her boss: "We don't have customers in Luxembourg".

    Important: Under normal conditions, the assistant wouldn't give an empty, white piece of paper to the boss to signal the fact that there are no customers, would he?

  3. The assistant can't execute the query because of a technical problem (for example: he forgot his password (because he changed it in the morning (but forgot to write it down on a post-it that is kept at a secret place (i.e. stuck on the front of his desktop monitor)))):

    The assistant tells his boss "Sorry, I couldn't print the list because ...".

Now the correct answers to our software design questions become pretty much obvious, don't they?

  1. No need to discuss the first case. If there are customers in Luxembourg, then getCustomersByCity returns a non-empty list.

  2. If we model the real world, then we obviously return null in case 2 (no customers in Luxembourg). Returning an empty list would be like the assistant handing over an empty piece of paper to his boss. Nobody would do this in real life - it wouldn't make sense, at least not under 'normal' conditions.

    And there is no need to do it in software, unless we have a very good reason to do so.

    For example, imagine that the boss really needs a piece of paper, even if there are no customers. He/she wants a document like this one ...

    ... as a 'proof' to be filed somewhere.

    If we want to model this, we might be tempted to actually return an empty list. But a better solution would be to return a non-null object implementing the following Java interface:

    Java
    interface ICustomerByCityReport {
    
       public String getCity();
    
       public Date getDateOfReport();
    
       public List<ICustomer> getCustomers();
    }

    In case of no customers for a given city, the method would return a non-null ICustomerByCityReport object with getCustomers() returning null.

  3. Case 3 (i.e. the operation couldn't be executed because of a technical problem) is less obvious. Should we simply return null? No! Because that would be like the assistant telling his boss: "There are no customers in Luxembourg.". Obviously, the information "There are no customers in Luxembourg." is semantically very different from "I couldn't print the list because of a technical problem. I can't tell you if we have customers in Luxembourg or not". This is an important distinction for the boss, and only he/she can decide what to do. The same is true for client code that calls getCustomersByCity. The information "there was a technical problem" must be forwarded to the client and it is the client's role to decide what to do.

    How to do this depends on the programming language we use.

    In languages that support an exception mechanism (C#, Java, etc.), we would throw an exception if the data cannot be retrieved from the database (e.g. connection to database could not be established).

    In other languages (e.g. languages that support multiple output arguments or tuples) we might return two values - the first one being the result and the second one being the error. The following return states are then possible:

    • The operation succeeded and results were found: result holds a (non-empty) list and error is null

    • The operation succeeded but no results were found: result and error are null

    • The operation could not be executed: result is null and error contains an object describing the error

Empty Lists in Real Life

If we look around us in the physical world, we can quickly see that empty lists are very rare in real life. Most often, we either have a non-empty list or we have no list at all. For example, you might have a to-do list. If you have one, it is probably (like mine) not empty.

However, it is obvious, but also interesting to note that empty lists occur each time we start to create a non-empty list.

Imagine Alice jotting down a shopping list for Bob:

  1. She takes a piece of paper which is empty - an empty list.
  2. She writes down all the items to buy. At the end, the list is non-empty. Moreover, during the writing process, the list is mutable - items are added (and some might be removed or modified).
  3. Once the list has been created, it becomes immutable - the list doesn't change anymore.

This is how we typically proceed in real life. And this reflects exactly how we should proceed in software. Here is a trivial example in Java:

Java
public static List<String> getShoppingList() {

   // 1. create a mutable empty list
   List<String> result = new ArrayList<String>();

   // 2. populate the list      
   result.add ( "Almonds" ); 
   result.add ( "Coconut oil" );
   result.add ( "Avocado" );
   result.add ( "Blueberries" );

   // 3. return an immutable, non-empty list
   return Collections.unmodifiableList ( result );
}

Conclusion

Immutable empty lists are very rare in real life. Normally, we either have a non-empty list or there is no list at all.

The corollary is that we shouldn't use immutable empty lists in our software applications, unless there is an exceptional case. This matches our conclusion from the previous installment and confirms that it is generally better to return null instead of an empty list.

In the next installment, we will look at real-world source code examples. We will have a look at typical cases that happen frequently in practice. And we will see how to treat exceptional situations, such as invalid input argument values and resource errors.

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

 
GeneralMy vote of 2 Pin
Elrond19-Nov-14 6:34
Elrond19-Nov-14 6:34 
GeneralRe: My vote of 2 Pin
ChristianNeumanns18-Dec-14 20:43
mvaChristianNeumanns18-Dec-14 20:43 
GeneralMy vote of 2 Pin
wout de zeeuw13-Nov-14 3:54
wout de zeeuw13-Nov-14 3:54 
QuestionIt's not really about lists. Pin
wout de zeeuw13-Nov-14 3:39
wout de zeeuw13-Nov-14 3:39 
GeneralRe: It's not really about lists. Pin
PIEBALDconsult13-Nov-14 3:46
mvePIEBALDconsult13-Nov-14 3:46 
AnswerRe: It's not really about lists. Pin
ChristianNeumanns16-Nov-14 17:44
mvaChristianNeumanns16-Nov-14 17:44 
QuestionBad advice based on wrong interpretation of examples Pin
icokk89-Nov-14 4:39
icokk89-Nov-14 4:39 
AnswerRe: Bad advice based on wrong interpretation of examples Pin
ChristianNeumanns9-Nov-14 21:22
mvaChristianNeumanns9-Nov-14 21:22 
icokk8 wrote:
In all my experience with programming I have very rarely needed to handle empty lists specially.

That is amazing, because there are so many situations in which the cases "there are data" and "there are no data" are semantically very different and therefore need to be treated differently. It is easy to come up with uncountable examples:
- if "we have customers in Singapore" then we have to plan a trip to Singapore, otherwise we haven't.
- if the list of available rooms in a hotel is empty then we have to choose another hotel
- if the classroom is empty then: switch off the lights, close the windows, etc.
- if my to-do list is empty then I can go for a walk
- etc. etc.

icokk8 wrote:
You claim that it is better to return null than empty list, because than you just have to check for null and not for both null and empty list.

Yes, I profoundly believe it is better to return null, but not for the reason you mention.

The main reason is this:

If we forget to make a difference between the two semantically different cases of "there are data" (i.e. a non-empty list) and "there are no data" (i.e. an empty list or null) then the average risk for bad outcomes is less high if we return null. The reason for this is explained in the article series (especially part 2[^] and part4[^]) and illustrated with concrete source code examples. In short:
Forgetting to check for null typically leads to a null pointer error which can't stay unnoticed and:
- leads to an application crash (which is of course undesirable, but (in most cases) less disastrous than continuing execution with wrong data and/or wrong behavior)
- is detected early in the development process
- is easy and cheap to locate and repair
On the other hand, forgetting to check for an empty list often leads to the program silently continuing execution, possibly with wrong data and/or wrong assumptions, ending up in really bad situations (example: the doctor assumes the patient has no allergies, because the list of allergies reported by the software is empty, because an allergy test has not yet been done). This kind of bugs are less likely to be discovered before software delivery and sometimes they are difficult to locate (because of the far distance in time and source code location of the cause and effect).

Another advantage of returning null is that null is always cheap in time and space (see chapter 'Time and space requirements' in part 2[^]).

Finally, there is another unbeatable advantage that exists in languages with compile-time null-safety: Forgetting to check for "no data" is no more an issue, because the compiler gently reminds us to do so. For a concrete example, you might want to have a look at chapter 'Example 4' in part 4[^].

icokk8 wrote:
What you usually do is loop over the list and that works for empty list just as well as for any other.

Yes, that is why people like to work with empty lists: Just loop over the list and don't worry about the list being empty (and the potential for bad consequences), and don't worry about null pointer errors.

icokk8 wrote:
I'm certain many of its users wanted to demolish the authors car after their program died in production because of a null pointer exception

Might be, but the authors car would have been saved in a language with compile-time null-safety. Smile | :)

icokk8 wrote:
When you present a list to users, you usually show some header before elements so an empty list is just a header without any data, which is quite understandable to anybody.

It might be understandable for some users. But others might wonder: "Is the list really empty or was there a problem printing the list?".
On the other hand, making a difference by explicitly printing a message like "There are no data" eliminates the ambiguity.

icokk8 wrote:
most view templating engines have syntax for special presentation of empty lists

Yes, and this is good! So, these templates differentiate between "data" and "no data"

icokk8 wrote:
On the other hand, if you have null instead of empty list, the users will quite likely receive "500 Internal server error"

Yes, and the user will not be happy. But:
This bug is more likely to be detected before delivery, because the programmer/tester quickly gets aware of it the first time it happens before delivery. Also, the bug is more likely to be fixed quickly after delivery if there is a good error logging/reporting system in place. Moreover displaying "500 Internal server error" is not nice, but less painful than displaying wrong data or continuing with wrong behavior due to wrong assumptions.

icokk8 wrote:
But what happens when somebody enters only part of data, e.g. first 10 out of 10000 votes? Your results are wrong again.

Yes, then we get wrong results, whether we return null or an empty list.
Using null and compile-time null-safety helps to detect some bugs early. But in this case it's not a software bug - it's a data entry error. And some kinds of user entry errors (e.g. entering a list only partially) can't be detected by software and software cannot be blamed for such problems.

icokk8 wrote:
In programming, special cases are always a maintenance burden

I fully agree.
And that's why I advocate using null, ideally in a null-safe language. Because some corner cases will be discovered and reported by the compiler if we forget to handle them separately. For an example see chapter 'Example 4' in part 4[^].

Cheers
GeneralRe: rethink your arguments Pin
D4rkTrick3-Jan-15 11:38
professionalD4rkTrick3-Jan-15 11:38 
GeneralRe: rethink your arguments Pin
ChristianNeumanns4-Jan-15 18:08
mvaChristianNeumanns4-Jan-15 18:08 
GeneralPrinciple PinPopular
Alexandru Lungu22-Sep-14 5:17
professionalAlexandru Lungu22-Sep-14 5:17 
GeneralRe: Principle Pin
ChristianNeumanns22-Sep-14 23:56
mvaChristianNeumanns22-Sep-14 23:56 
GeneralRe: Principle Pin
mrcellux23-Jan-15 12:12
mrcellux23-Jan-15 12:12 
GeneralRe: Principle Pin
Alexandru Lungu24-Jan-15 9:32
professionalAlexandru Lungu24-Jan-15 9:32 
GeneralRe: Principle Pin
mrcellux24-Jan-15 9:58
mrcellux24-Jan-15 9:58 
GeneralRe: Principle Pin
Alexandru Lungu25-Jan-15 2:17
professionalAlexandru Lungu25-Jan-15 2:17 
GeneralRe: Principle Pin
mrcellux25-Jan-15 5:01
mrcellux25-Jan-15 5:01 
GeneralRe: Principle Pin
Alexandru Lungu26-Jan-15 3:24
professionalAlexandru Lungu26-Jan-15 3:24 
GeneralRe: Principle Pin
mrcellux31-Jan-15 9:10
mrcellux31-Jan-15 9:10 
QuestionClose, but not quite. Pin
Alexander Wieser19-Sep-14 14:24
Alexander Wieser19-Sep-14 14:24 
AnswerRe: Close, but not quite. Pin
ChristianNeumanns21-Sep-14 18:05
mvaChristianNeumanns21-Sep-14 18:05 
GeneralRe: Close, but not quite. Pin
Alexander Wieser22-Sep-14 7:12
Alexander Wieser22-Sep-14 7:12 
GeneralRe: Close, but not quite. Pin
ChristianNeumanns23-Sep-14 0:20
mvaChristianNeumanns23-Sep-14 0:20 
GeneralRe: get or getDeliveryDate is a bad example here Pin
D4rkTrick4-Jan-15 11:03
professionalD4rkTrick4-Jan-15 11:03 
AnswerRe: Close, but not quite. Pin
wout de zeeuw13-Nov-14 4:02
wout de zeeuw13-Nov-14 4:02 

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.