In this post, we’ll explore multiple ways to check a Go interface value for nil, and try to expain why you should probably not check them at all. Let’s start with an example to introduce the problem.

In Go, when we assign a typed nil value to an interface, the interface will not be equal to nil. Consider the following, We define a Sender interface with two implementations, an EmailSender and SMSSender:

type Sender interface {
	Send(message string) error
}

type EmailSender struct {
	FromAddress string
}

func (es *EmailSender) Send(message string) error {
	fmt.Printf("sending email from %v", es.FromAddress)
	return nil
}

type  SMSSender  struct {
	FromAddress string
}

func (sms *SMSSender) Send(message string) error {
	fmt.Printf("sending sms from %v", sms.FromAddress)
	return  nil
}

we also have a function that accepts the interface as an argument:

func SendMessage(sender Sender, message string) error {
	if sender == nil {
		return fmt.Errorf("sender is nil")
	}
	return sender.Send(message)
}

All is good, until we call our function with a nil value:

func main() {
	var sender *EmailSender
	// this call will panic
	SendMessage(sender, "hello world!")
}

The SendMessage function calls the Send method which causes the program to panic when accessing FromAddress on the nil sender. But why hasn’t the function returned right away in the if sender == nil check?

This is a common pitfall for new Go developers especially when dealing with errors. The reason for this behavior is, that an interface value will equal to nil only when the underlying type and value are both equal to nil. In the above example, since the value of sender does have a type *EmailSender, even though the value is nil, it will not be considered equal to nil.

At this point, a question arises:

How can we ensure that the value of an interface is not nil?

We should aim for robust code where we handle errors gracefully, and try to avoid runtime panics at all costs. Is there a way to guard our SendMessage function, and check whether the value of sender is nil or not? Let’s explore our options.

Using type assertion

We can solve this problem by using type assertions:

if sender == nil {
		return fmt.Errorf("sender is nil")
}
if t, ok := sender.(*EmailSender); ok && t == nil {
	return fmt.Errorf("sender is nil")
}
if t, ok := sender.(*SMSSender); ok && t == nil {
		return fmt.Errorf("sender is nil")
}

this works, but it’s very repetitive and tedious to write. Can we simplify these checks with a type switch?

Type switch

Let’s try to simplify it with a simple switch statement:

switch v := sender.(type) {
case (*EmailSender):
	if v == nil {
		return fmt.Errorf("sender is nil")
	}
case (*SMSSender):
	if v == nil {
		return fmt.Errorf("sender is nil")
	}
case nil:
	return fmt.Errorf("sender is nil")
}

This works as well, but is definitely less readable than the previous example. While the previous version was 9 lines of code, this version has 12 lines, and we now have an extra level of identation. Furtunately, we can group many switch cases in a single line:

switch v := sender.(type) {
case (*EmailSender), (*SMSSender), nil:
	if v == nil {
		return fmt.Errorf("sender is nil")
	}
}

I was surprised to get a result of panic: runtime error: invalid memory address or nil pointer dereference, the nil check now returns false! Why is that? It turnes out that this is the expected behaviour of grouped cases in a type switch. The language specification clearly states:

In clauses with a case listing exactly one type, the variable has that type; otherwise, the variable has the type of the expression in the TypeSwitchGuard.

In other words, in our first example each clause had just a single case, so the value of v had the type of that case, enabling our typed nil check. But in the second example, since we had multiple cases in the clause, the value of v fell back to be of type Sender which is an interface, so checking v == nil was nil, for the same reason we saw earlier, interfaces are nil only when thy have no underlying type.

Type assertion won’t do the job for us. Let’s try something different.

Comparing with typed nil values

We can use a regular comparison check to check if our value is nil:

if sender == nil || sender == (*EmailSender)(nil) {
	return fmt.Errorf("sender is nil")
}

And luckily, we can beutifully simplify this approach with a simple switch statement:

switch sender {
case (*EmailSender)(nil), (*SMSSender)(nil), nil:
	return fmt.Errorf("sender is nil")
}

This is a nice and clean solution and could have worked if the SendMessage function would have be unexported, preventing our library consumers from calling it with yet another implementation. However, since our function is exported, users can pass any type which satisfies Sender and we have no way of knowing beforehand which type sender will be. This makes our solution practically useless.

Having the nil check as part of the interface contract

I have seen people suggest that we solve these problems by updating our interface definition to include a method which checks for nil:

type Sender interface {
	Send(message string) error
	IsNil() bool
}

and now have each implementation tell us whether it is nil or not. For example, our EmailSender will now need to add a method:

func (es *EmailSender) IsNil() bool {
	return es == nil
}

and we can simply check

if sender == nil || sender.IsNil() {
	fmt.Printf("sender is nil")
	return
}

Don’t do this. It is not what idiomatic Go looks like, and this doesn’t make any sense when you think about it. An interface should describe just the functionality of what it is capable of doing and nothing else. Nil checking is not part of the functionality you expect from a message sender and nil checking is an internal programming implementation detail.

Reaching for reflection

We can use the reflect package to help us solve our problem:

if sender == nil || reflect.ValueOf(sender).IsNil() {
	fmt.Printf("sender is nil")
	return
}

The above will work most of the time, but most of the time is not enough. The check itself may panic if the value of sender is a value and not a pointer. To see this in action, just change the method receiver of SMSSender.Send to a value instead of a pointer:

func (sms SMSSender) Send(message string) error {
	fmt.Printf("sending sms from %v", sms.FromAddress)
	return  nil
}

Now when you call SendMessage(smsSender), the call panics. This is due to the fact that IsNil may only be called on types chan, func, interface, map, pointer, or slice. We can fix this with yet another reflect function to ensure we call IsNil for valid values only.

if sender == nil {
	fmt.Printf("sender is nil")
	return
}
switch  v := reflect.ValueOf(sender); v.Kind() {
case reflect.Ptr, reflect.Map, reflect.Chan, reflect.Slice, reflect.Func:
	if v.IsNil() {
		fmt.Printf("sender is nil")
		return
	}
}

Note, there is no need to check for reflect.Interface in our case, as it’s impossible for the sender value to be an interface type. This is due to the fact that a method receiver cannot be an interface.

It’s kind of ugly to deal with reflection, and reflection is never clear, but at least it does the job and it works. This seems to be the only proper solution for such a use case.

But, let’s take a step back.

Are nil values dangerous?

Go allows to call methods on nil pointers. For example, we can update our interface implementation so it checks if the receiver is nil, and if it is, it uses a default value instead of accessing it:

type  EmailSender  struct {
	FromAddress string
}
func (es *EmailSender) Send(message string) error {
	addr := "default address"
	if es != nil {
		addr = es.FromAddress
	}
	fmt.Printf("sending email from %v", addr)
	return  nil
}

Now we can safely pass our nil EmailSender to the SendMessage function. We can remove the typed nil check and it won’t panic, it will instead fall back to the default predefined behavior. Now should you reject nil inputs? Of course not! Nil values are perfectly valid and they can be a handy technique in your toolbox.

What about nil interfaces, should you check for the case of if sender == nil? Calling Send on a nil Sender will panic after all. Let’s look into the standard library for inspiration.

How the standard library handles nil interfaces

The Go standard library defines quite a few interfaces. Let’s check out the fs.ReadDir function which requires an io.FS (an interface) as its first argument. If you run fs.ReadDir(nil, "") you get panic: runtime error: invalid memory address or nil pointer dereference. The function does not do any nil check on its argument. Why is that, isn’t it a good practice to validate our inputs to avoid runtime errors?

It is up to the caller

The answer to that is, that when you call a function that accepts a pointer or an interface, unless otherwise documented, you should assume by default that passing nil to it will cause a panic. A function may explicitly support nil values, but it must be clearly documented. For example, the http.ListenAndServe function documents its behavior as follows: The handler is typically nil, in which case the DefaultServeMux is used, so nil is valid here, but otherwise you as the caller are expected to ensure that your values are non-nil.

So, when a user passes nil to our function the panic is no problem, it’s the callers problem. The best way to handle it is by panicing and letting them know that they used it incorrectly. That’s what panics are for, and I would argue that sprinkling your entire codebase with unnecessary nil checks harms readability and is a bad code smell.

As a side note, the same rule applies to method receivers. Don’t check your receivers for nil out of fear that someone accidentally forgot to initialize them. Unless you explicitly support it, it’s up to the caller to ensure that the value is not nil.

I don’t trust my users

If you don’t trust your users and want to protect them from panicing, checking for nil won’t help. If your user is not careful and introduces bugs in their code, you may still cause a panic when calling their interface implementation even if you do check for nils. There may be plenty of other internal problems causing the runtime to blow up. For example, the method on the struct they passed you may internally try to access a method on a nil field on the struct, or do some other thing that may cause the runtime to error out. You can never account for all cases. You have no other choice but to trust your user in these cases, and the panic will let them know if the made a mistake in their code.