DEV Community

Cover image for Tricky Golang interview questions - Part 3: nil receivers
Harutyun Mardirossian
Harutyun Mardirossian

Posted on

Tricky Golang interview questions - Part 3: nil receivers

I have one more tricky interview question to discuss in this series. This one is related to function receivers and methods in golang, often called nil receivers.

Question: What is the output of the following?

package main

import "fmt"

type gopher struct {
    name string
}

func (r *gopher) print() {
    fmt.Println("gopher-printer works!")
}

func main() {
    var gpr *gopher
    gpr.print()
}
Enter fullscreen mode Exit fullscreen mode

A quick note here, this question has one of the highest failing rates when asked during the interviews. Most of the interviewees never tried or never experienced such behaviour.

Receiver

A receiver argument is what distinguishes a method from a regular function in GoLang. In essence, a method is simply a function that includes a receiver argument. A receiver can also be of a non-struct type. For example, in the code below, we have a method add() with a receiver of type Integer, which is an alias for int.

type Integer int

func (i Integer) add(j Integer) Integer {
    return i + j
}
Enter fullscreen mode Exit fullscreen mode

Okay, that's a pretty straightforward representation of what a receiver is. Now, let's try answering this question. If you come from OOP languages like Java, C# or C++ you are familiar with the term null pointer exception.  It means that you are trying to access a part of something that doesn't exist. Here's the classic example from Java:

public class NullPointer f
    public static void main(String[] args) {
        String a = null;
        System.out.println(a.length());
    }
}
Enter fullscreen mode Exit fullscreen mode

Trying to compile this will throw the null pointer exception:

Exception in thread "main" java.lang.NullPointerException
        at NullPointer-main (NullPointer.java:7)
Enter fullscreen mode Exit fullscreen mode

The developer familiar with this concept will shortly answer:
The program will panic with nil pointer dereference if we try to run this.

panic: runtime error: invalid memory address or nil pointer dereference
Enter fullscreen mode Exit fullscreen mode

Let's actually run the program and make sure.

[Running] go run "main.go"

gopher-printer works!

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

Here, most interviewees get confused by this behaviour and assume that the Go compiler initialises the struct when we create the variable with the type var gpr *gopher. To test this let's add a print line inside the print() function like this:

func (r *gopher) print() {
    fmt.Printf("receiver: %v \n", r)
    fmt.Println("gopher-printer works!")
}
Enter fullscreen mode Exit fullscreen mode

and build the program again.

[Running] go run "main.go"

receiver: <nil>
gopher-printer works!

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

Oops, the struct is not initialised, its value is nil. Again, if you come from OOP languages this may seem like a static method call, but it's far different from that concept. You see, receivers in Go act like a basic argument, and in this case, when we call a method on an uninitialised struct, inside the methods nil has passed as a receiver as if you do something like this instead:

package main

import "fmt"

type gopher struct {
    name string
}

func print(r *gopher)  {
    fmt.Println("gopher-printer works!")
}

func main() {
    var gpr *gopher
    print(gpr)
}
Enter fullscreen mode Exit fullscreen mode

The nil receiver is just a parameter.

After we clear this out, you give a confident answer to the interviewer:
This will print a message "gopher-printer works!" into the console and will not cause a compile time panic
It's that easy!

Here some interviewers like to ask the following:
What will cause the program to panic?

The answer is very simple. Since the nil receiver is just a parameter, this panic will only occur if the method tries to access the struct fields, such as name in our case.

type gopher struct {
    name string
}

func (r *gopher) print() {
    fmt.Println(r.name) // <-- accessing the field
    fmt.Println("gopher-printer works!")
}
Enter fullscreen mode Exit fullscreen mode

So the full answer to this tricky question is:
When you call this method from a nil pointer you will not get any compiler errors but you may get runtime panic if the method tries to access the struct fields.

Bonus section

Another interesting point to note is that a nil receiver can be initialized inside the method. However, this initialization has no effect outside of that method. Check the following code, I initialise the receiver inside the method:

package main

import "fmt"

type gopher struct {
    name string
}

func (r *gopher) print() {
    // init the receiver with a new struct and populate the field
    if r == nil {
        r = new(gopher)
        r.name = "crusty0gphr"
    }

    fmt.Printf("hi there %s \n", r.name)
}

func main() {
    var gpr *gopher
    gpr.print()
}
Enter fullscreen mode Exit fullscreen mode

The result of this program will be:

[Running] go run "main.go"

hi there crusty0gphr

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

But inside the main function, the variable gpr will remain nil and accessing its field will cause a compile time panic!
Now, It's that easy, finally!

Top comments (0)