DEV Community

Cover image for Tricky Golang interview questions - Part 5: interface == nil
Harutyun Mardirossian
Harutyun Mardirossian

Posted on

Tricky Golang interview questions - Part 5: interface == nil

Another topic I would like to discuss in these series is the interface to nil comparison problem. This question is frequently asked during many golang interviews and requires an understanding of how types are constructed under the hood.

Question: Which if statements will be evaluated as true?

package main  

import "fmt"  

type SomeType interface {  
    Get()  
}  

type SomeImpl struct {...}  
func (i SomeImpl) Get() {...}  

func main() {  
    var aType SomeType  
    if aType == nil {
       fmt.Println("nil interface")  
    }  

    var aImpl *SomeImpl  
    if aImpl == nil {
       fmt.Println("nil struct")  
    }  

    aType = aImpl  
    if aType == nil {
       fmt.Println("nil assignment")  
    }  
}
Enter fullscreen mode Exit fullscreen mode

As we all know (hope so) the empty interface in golang holds a nil value. The default value of an empty interface is nil. This means the uninitialised variable var aType SomeType holds a nil value because it has a type of empty (nil) interface. In this case, the if condition is fulfilled, we will see a print statement in the terminal:

[Running] go run "main.go"

nil interface
Enter fullscreen mode Exit fullscreen mode

Good, let's continue further.
We have another uninitialised variable var aImpl *SomeImpl which is a pointer to the struct. As you know in golang all memory is initialised (zeroed) this means that the pointers, even if they are uninitialised, will have a default value, which is nil. So we have one more fulfilled if condition:

[Running] go run "main.go"

nil interface
nil struct
Enter fullscreen mode Exit fullscreen mode

For the last, we see a value assignment (value initialisation) of a struct pointer value to the interface variable aType = aImpl. Judging by the earlier made statements it is logical to assume that we assign a nil value to the var aType and in the result, aType will remain nil.

[Running] go run "main.go"

nil interface
nil struct
nil assignment

[Done] exited with code=0 in 0.318 seconds
Enter fullscreen mode Exit fullscreen mode

This sounds logical, so the confident answer is:
The program will output all print statements into the terminal because all if statements will be fulfilled during the execution!

Okay, that sounds good (would say the interviewer). Let's run the program and check the results. The actual result looks like this:

[Running] go run "main.go"

nil interface
nil struct

[Done] exited with code=0 in 0.318 seconds
Enter fullscreen mode Exit fullscreen mode

As you already noticed, the output does not contain the last nil assignment print statement. So, why this happened?
To answer this question we must dig deeper into the language to understand how interfaces are constructed in golang.

Interface

In golang, an interface is a type that specifies a set of method signatures. When a value is assigned to an interface, golang constructs an interface value that consists of two parts: the dynamic type and the dynamic value. This is commonly referred to as the “interface tuple.”

  1. Dynamic Type: This is a pointer to a type descriptor that describes the type of the concrete value stored in the interface.
  2. Dynamic Value: This is a pointer to the actual value that the interface holds.

The interface tuple can be represented as the following structs:

type iface struct {
    tab  *itab
    data unsafe.Pointer
}

type itab struct {
    inter *interfacetype
    _type *_type
    hash  uint32
    _     [4]byte
    fun   [1]uintptr  // variable sized, actually [n]uintptr
}
Enter fullscreen mode Exit fullscreen mode
  • tab: A pointer to an itab structure that contains information about the type and the methods that the type implements for the interface.
  • data: A pointer to the actual data held by the interface.

When a value is assigned to an interface, golang finds the type descriptor for the concrete type being assigned to the interface. Then sets up the method table (itab) that allows method calls through the interface to be dispatched to the correct implementation and finally stores a pointer to the actual value in the data field of the interface.

When aType = aImpl is executed

  1. Determining Interface Implementation: golang first determines that *SomeImpl (a pointer to SomeImpl) implements the SomeType interface because *SomeImpl has a method Get() with the correct signature.
  2. Looking Up the Type Descriptor: golang looks up the type descriptor for *SomeImpl.
  3. Creating the itab: golang creates an itab structure
  4. Assigning the Pointer: golang assigns the pointer to the SomeImpl value to the data field of the interface.
interface (aType)
+------------+           +-----------+
| tab        |---------->|  itab     |
|            |           |-----------|
| data       |--+        |  inter    |
+------------+  |        |  _type    |
                |        |  fun[0]   |
                |        +-----------+
                |
                v
         +---------------+
         |   *SomeImpl   |
         +---------------+
         |   ........    |
         +---------------+
Enter fullscreen mode Exit fullscreen mode

Summary

To sum up, what we learned, the previous explanation in short means:

+ uninitialised +-------------+ initialised +

interface (aType)             interface (aType)
+------------+                +--------------------------------+
| tab:  nil  |                | tab:  type of *SomeImpl        |
| data: nil  |                | data: value of *SomeImpl (nil) |
+------------+                +--------------------------------+
Enter fullscreen mode Exit fullscreen mode

Now the tricky part.
In golang when checking if an interface is nil, both the tab and data fields must be nil. If an interface holds a nil pointer of a concrete type, the tab field will not be nil, so the interface itself will not be considered nil.

This is the reason why we don't see the last print statement inside the terminal. After execution of aType = aImpl the variable aType is no longer considered as a nil / empty interface.

It's that easy!

Top comments (0)