Tricky Golang interview questions. Part 1: Slice Header
Tags: go, interview, programming, tutorial
Published: 2024-05-22 09:08:26

This series is for those who are already familiar with Go and want to prepare for challenging and tricky interview questions. We'll dive deep into advanced topics, best practices, and common pitfalls to ensure you're ready to tackle the toughest questions with confidence.

In each article, we will explore hidden flaws and techniques around standard Go practices, uncovering the nuances and subtleties that can make a big difference in your understanding and application of the language. By examining these less obvious aspects of Go, you'll gain a deeper insight into the language's inner workings and be better prepared to impress in your interviews. 

Let’s get started and master the intricacies of Go together!

An example I want to discuss is quite simple and requires knowledge about how arguments are passed in Go and how the append function works.

**Question: What is the output of the `fmt.Println`?**
```go
package main

import (
	"fmt"
)

func main() {
	s := make([]int, 0, 2)

	doSomething(s)
	fmt.Println(s)
}

func doSomething(a []int) {
	a = append(a, 1)
}
```
A quick note here:
> In Golang, **function arguments are passed by value**, which means that a copy of the argument value is created and passed to the function.

This quote is straightforward and self-explaining but there's a **nuance**. The nuance is how slices are constructed under the hood. You see, slices are a more complex data structure. The slice can be represented as the following struct:
```go
type SliceHeader struct {
    Data uintptr
    Len  int
    Cap  int
}
```
Slices have headers containing the length, capacity and the pointer to the underlying array. When we pass a slice as a function argument we actually copy the `SliceHeader` with all its values. Since the `SliceHeader.Data` contains a pointer copying this address keeps the underlying array the same for both `SliceHeaders`. **So appending to the copied slice will also modify the original slice. Right?** 

Let's run the program and check the results:
```bash
[Running] go run "main.go"
[]
[Done] exited with code=0 in 1.377 seconds
```
Let's see what happened here. 
![Slice diagram](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i95d6dd8w8pbums43f7y.png)
This diagram shows that inside the `f.doSomething()` we created a new copy of the `SliceHeader`. The difference between the headers is the length fields, it was 0 and now it's 1. The append function calculates the new length after appending the data. If this length exceeds the current capacity, it reallocates a new slice with double the required capacity, copies the old slice into it, and then appends the new data. Finally, it returns the updated slice. Let's print out the slice headers inside both functions using the `unsafe` package to make sure: 
```go
...
sh := (*SliceHeader)(unsafe.Pointer(&s)) // you must define a struct of the header for this to work
fmt.Println(sh)
...
```
The output is: 
```bash
&{1374389592336 0 2} // main
&{1374389592336 1 2} // doSomething
```
Both headers indeed point to the same underlying array, but because they have different lengths, they are two different slices. So since the `SliceHeader` inside the `f.main()` function has a `len=0` `fmt.Println()` function prints an empty slice.
```
// main             |    // doSomething
--------------------+----------------------
&{                  |    &{
    1374389592336   |        1374389592336
    [0    <-- length  -->    1]
    2               |        2
}                   |    }
```
___
So the answer to this interview question is simple: 
**Program will output an empty slice because the slice inside the main function has a length of 0.**
___
The tricky part doesn't end up here. There is a way to print out the slice values. We can slice the slice inside the `f.main()` function to the first element and have a valid output. Take a look at this:
```go
package main

import (
	"fmt"
)

func main() {
	s := make([]int, 0, 2)

	doSomething(s)
	fmt.Println(s[:1]) // <-- here I sliced the slice from 1st to 2nd element
}

func doSomething(a []int) {
	a = append(a, 1)
}
```
And voila, we successfully printed the element from the underlying array.
```bash
[Running] go run "main.go"
[1]
[Done] exited with code=0 in 1.377 seconds
```
This happened because we sliced the slice. **Slicing is the process of taking a portion of a slice and creating a new slice from it**.  Once again we copied the `SliceHeader` with a new length. In this case, we can slice the original slice inside the `f.main()` because the capacity of the slice is greater than zero: `cap(s) > 0 // <-- true`.