Tricky Golang interview questions - Part 3: nil receivers
Tags: go, interview, tutorial, programming
Published: 2024-05-31 17:04:28

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?**
```go
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()
}
```

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`.
```go
type Integer int

func (i Integer) add(j Integer) Integer {
	return i + j
}
```

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: 
```java
public class NullPointer f
    public static void main(String[] args) {
        String a = null;
        System.out.println(a.length());
    }
}
```
Trying to compile this will throw the `null pointer exception`:
```bash
Exception in thread "main" java.lang.NullPointerException
		at NullPointer-main (NullPointer.java:7)
```

The developer familiar with this concept will shortly answer:
**The program will panic with nil pointer dereference if we try to run this.**
```go
panic: runtime error: invalid memory address or nil pointer dereference
```

Let's actually run the program and make sure.
```go
[Running] go run "main.go"

gopher-printer works!

[Done] exited with code=0 in 0.318 seconds
```

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: 
```go 
func (r *gopher) print() {
	fmt.Printf("receiver: %v \n", r)
	fmt.Println("gopher-printer works!")
}
```
and build the program again.
```go
[Running] go run "main.go"

receiver: <nil>
gopher-printer works!

[Done] exited with code=0 in 0.318 seconds
```

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:
```go
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)
}
```
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.
```go 
type gopher struct {
	name string
}

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

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:
```go
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()
}
``` 
The result of this program will be:
```go
[Running] go run "main.go"

hi there crusty0gphr

[Done] exited with code=0 in 0.318 seconds
```

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!