Skip to content

Go Cheat Sheet For Coding Interviews

Posted on:January 18, 2024 at 08:27 PM

Table of Contents

Open Table of Contents

Introduction

Let’s admit it, coding interviews sucks 💩. Most of the time, the kind of questions you have to deal with are not related to the “real” challenges you face on your daily basis. However, we can’t deny the reality, it’s what it’s and we have to master this skill like anything else in our career as software engineers.

In this post, I’ll give you some common tricks, patterns and data structures that you come across in coding interview processes for Go (a.k.a Golang) positions, so let’s have some fun!

Variables

No matter what problem you’re trying to solve, variables are something you’re going to need all the time. Here is how we use them in Go:

package main

import "fmt"

func main() {
	// Defining a variable
	language := "Golang"
	fmt.Println(language) // Golang

	// Set it to the `zero` value, in this case an empty string
	var sentence string
	fmt.Printf("%q\n", sentence) // ""

	// Defining multiple variables at the same time
	slow, fast := 0, 10

	fmt.Println(slow, fast) // 0 10

	// Swapping variables (or arrays values)
	slow, fast = fast, slow
	fmt.Println(slow, fast) // 10 0
}

Execute the code in this playground

If statements

For the if statements we don’t need parentheses (( or )) around the condition.

package main

import "fmt"

func main() {
	age := 30

	if age < 18 {
		fmt.Println("You are a boy 👦🏽")
	} else if age >= 18 && age <= 65 {
		fmt.Println("You are an adult 👨🏽") // You are an adult 👨🏽
	} else {
		fmt.Println("You are old 👴🏽")
	}
}

Execute the code in this playground

For loop

The for loop in Go is similar to other languages, but it doesn’t have the while keyword, so you have to use the for loop to achieve the same behavior.

package main

import "fmt"

func main() {
	// Looping from i = 0 to i = 10
	for i := 0; i <= 10; i++ {
		fmt.Printf("%d ", i) // 0 1 2 3 4 5 6 7 8 9 10
	}

	fmt.Println()

	// Looping from i = 2 to i = 5 (the 6 is not included)
	for i := 2; i < 6; i++ {
		fmt.Printf("%d ", i) // 2 3 4 5
	}

	fmt.Println()

	// Looping in reverse from i = 5 to i = 2 (not including 1)
	for i := 5; i >= 2; i-- {
		fmt.Printf("%d ", i) // 5 4 3 2
	}

	fmt.Println()

	// Looping using the for range
	nums := []int{1, 2, 3, 4}
	for index, num := range nums {
		fmt.Printf("index: %d value: %d\n", index, num)
	}
}

Execute the code in this playground

Division

Go is a statically typed language, so when you divide two integers, the result is an integer, not a float. If you want a float division, you have to cast the values to float.

package main

import "fmt"

func main() {
	// Division is exact by default ⚠️
	fmt.Println(5 / 2) // 2

	// If you want a decimal division
	fmt.Println(float64(5) / float64(2)) // 2.5

	// Modding is similar to other languages
	fmt.Println(10 % 3) // 1
}

Execute the code in this playground

Talking about math, the math package and math’s related values are helpful when we are doing coding interviews, let’s see some examples:

package main

import (
	"fmt"
	"math"
)

func main() {
	// More math helpers
	fmt.Println(math.Floor(3 / 2)) // 1
	fmt.Println(math.Ceil(3 / 2))  // 1
	fmt.Println(math.Sqrt(9))      // 3
	fmt.Println(math.Pow(2, 8))    // 256

	// min and max int
	fmt.Println(math.MaxInt) // 9223372036854775807
	fmt.Println(math.MinInt) // -9223372036854775808
}

Execute the code in this playground

Sorting and reversing an array/slice

Go has a built-in package called sort that allows us to sort slices in ascending or descending order. Here is an example of how to reverse a slice and sort it in ascending and descending order:

package main

import (
	"fmt"
	"sort"
)

func main() {
	arr := []int{1, 2, 3, 4}

	// Reverse a slice
	for i := len(arr)/2 - 1; i >= 0; i-- {
		opp := len(arr) - 1 - i
		arr[i], arr[opp] = arr[opp], arr[i]
	}

	fmt.Println(arr) // [4, 3, 2, 1]

	// Sorting (in ascending order)
	arr2 := []int{4, 3, -2, -1}
	sort.SliceStable(arr2, func(i, j int) bool {
		return arr2[i] < arr2[j]
	})
	fmt.Println(arr2) // [-2, -1, 3, 4]

	// Sorting (in descending order)
	arr3 := []int{3, -2, -1, 4, 100, 0}
	sort.SliceStable(arr3, func(i, j int) bool {
		return arr3[i] > arr3[j]
	})
	fmt.Println(arr3) // [100, 4, 3, 0, -1,  -2]

	// Sorting an array of strings by alphabetical order
	names := []string{"Bob", "Alice", "Zoe", "Kevin"}
	sort.Strings(names)
	fmt.Println(names) // ["Alice", "Bob", "Kevin", "Zoe"]

	// Sorting an array of strings by length
	sort.SliceStable(names, func(i, j int) bool {
		return len(names[i]) < len(names[j])
	})
	fmt.Println(names) // ["Bob", "Zoe", "Alice", "Kevin"]
}

Execute the code in this playground

If you’re using Go 1.21 or greater, you can use the new slices package.

Strings

Strings in Go are a sequence of bytes, not characters. This means that when you access a string by index, you’re getting the byte value, not the character. Here are some examples of how to work with strings in Go:

package main

import (
	"fmt"
	"strconv"
	"strings"
)

func main() {
	// As arrays we can slice a string
	s := "Hello Gopher!"
	fmt.Println(s[0:5]) // "Hello"

	// Strings are immutable
	// s[0] = "h" // cannot assign to s[0] (neither addressable nor a map index expression)

	// We can "update" a string, but in reality what we are doing is
	// creating a new one (it's considered an O(N) time operation)
	s += ", nice to meet you."
	fmt.Println(s) // "Hello Gopher!, nice to meet you."

	// Valid numeric strings can be converted to integers
	a, _ := strconv.Atoi("123")
	b, _ := strconv.Atoi("10")
	fmt.Println(a + b) // 133

	// Numbers can be converted to strings and concatenate them
	c := strconv.Itoa(123)
	d := strconv.Itoa(10)
	fmt.Println(c + d) // "12310"

	// Getting the ASCII value from a char
	fmt.Println([]byte("A")) // 65

	// Combine a list of strings with an empty space delimiter
	e := []string{"Hello", "Gopher", "!"}
	fmt.Println(strings.Join(e, "")) // "HelloGopher!"
}

Execute the code in this playground

Queues (double ended queues)

A deque is a data structure that allows you to insert and remove elements from both ends. Go doesn’t have a native implementation of a deque, but we can use a list from the container package to achieve the same behavior. Here is an example of how to implement a deque in Go:

package main

import (
	"container/list"
	"fmt"
	"strconv"
)

// Deque represents a double ended queue.
type Deque struct {
	items *list.List
}

// NewDeque is a constructor that will declare and return the Deque type object.
func NewDeque() *Deque {
	return &Deque{list.New()}
}

// PushFront will push an element at the front of the Deque.
func (d *Deque) PushFront(val int) {
	d.items.PushFront(val)
}

// PushBack will push an element at the back of the Deque.
func (d *Deque) PushBack(val int) {
	d.items.PushBack(val)
}

// PopFront will pop an element from the front of the Deque.
func (d *Deque) PopFront() int {
	return d.items.Remove(d.items.Front()).(int)
}

// PopBack will pop an element from the back of the Deque.
func (d *Deque) PopBack() int {
	return d.items.Remove(d.items.Back()).(int)
}

// Front will return the element from the front of the Deque.
func (d *Deque) Front() int {
	return d.items.Front().Value.(int)
}

// Back will return the element from the back of the Deque.
func (d *Deque) Back() int {
	return d.items.Back().Value.(int)
}

// IsEmpty will check if the dequeue is empty or not.
func (d *Deque) IsEmpty() bool {
	return d.items.Len() == 0
}

// Length will return the length of the dequeue.
func (d *Deque) Length() int {
	return d.items.Len()
}

// Print prints the values inside the Deque.
func (d *Deque) Print() string {
	temp := d.items.Front()
	s := "["
	for temp != nil {
		temp2, _ := temp.Value.(int)
		s += strconv.Itoa(temp2)
		temp = temp.Next()
		if temp != nil {
			s += " , "
		}
	}
	s += "]"
	return s
}

func main() {
	d := NewDeque()

	d.PushFront(1)
	d.PushBack(2)
	d.PushBack(3)
	fmt.Println(d.Print()) // [1 , 2 , 3]
}

Execute the code in this playground

HashSet

Sometimes, in coding interview questions we need to keep a unique list of values and a set is the perfect data structure to achieve it. Sadly, Go doesn’t have a native way of achieving it, the following is an example of how to implemente a set using a map under the hood:

package main

import "fmt"

// Set represents a data structure of unique values.
type Set struct {
	values map[any]bool
}

// NewSet creates a new pointer to a Set.
func NewSet() *Set {
	return &Set{values: make(map[any]bool)}
}

// Add adds the given value to the Set. If the value already exists, it's override.
func (s *Set) Add(v any) {
	s.values[v] = true
}

// Has verifies if the given value exists in the Set.
func (s *Set) Has(v any) bool {
	_, ok := s.values[v]
	return ok
}

// Delete deletes the given value from the Set.
func (s *Set) Delete(v any) {
	delete(s.values, v)
}

// Length returns the length of the current items in the Set.
func (s *Set) Length() int {
	return len(s.values)
}

func main() {
	s := NewSet()

	s.Add(1)
	s.Add(1)
	s.Add(1)
	s.Add(2)
	s.Add(3)
	s.Add(3)
	s.Add(1)
	fmt.Println(s.values)   // map[1:true 2:true 3:true]
	fmt.Println(s.Has(1))   // true
	fmt.Println(s.Length()) // 3
}

Execute the code in this playground

HashMap (a.k.a map)

One the most (if not the most) common data structures while doing coding interviews is a map, this data structure allows us to keep a list of values in a key: value pair format.

package main

import "fmt"

func main() {
	// Defining a map
	myMap := map[string]int{}
	myMap["Alice"] = 23
	myMap["Bob"] = 34
	fmt.Println(myMap) // map[Alice:23 Bob:34]

	// Getting the length of the map
	fmt.Println(len(myMap)) // 2

	// Updating a key from the map
	myMap["Alice"] = 45
	fmt.Println(myMap) // map[Alice:45 Bob:34]

	// Checking a value exists in the map
	if val, ok := myMap["Alice"]; ok {
		fmt.Println("Alice value is:", val) // Alice value is: 45
	}

	if val, ok := myMap["Gabriel"]; !ok {
		fmt.Println("Gabriel value is:", val) // Gabriel value is: 0
	}

	// Deleting a key from a map
	delete(myMap, "Alice")
	fmt.Println(myMap) // map[Bob:34]

	// Initializing a map with values
	users := map[string]int{"Carlos": 10, "Gustavo": 30, "Juan": 97}
	fmt.Println(users) // map[Carlos:10 Gustavo:30 Juan:97]

	// Looping through a map with both the keys and values
	for name, age := range users {
		fmt.Println("Username:", name, "Age:", age)
	}

	// Looping only the values
	for _, age := range users {
		fmt.Println("Age:", age)
	}
}

In Go we call it map, but you can also see them as dict, associative arrays, objects, etc.

Execute the code in this playground

Heaps

Heaps are another really common data structure to find the min and max values from a set of elements, under the hood they are implemented using arrays.

package main

import (
	"container/heap"
	"fmt"
)

func main() {
	// Create the min heap data structure.
	minHeap := newMinHeap()

	// ℹ️ Note we se the `heap` package instead of the minHeap to push the items.
	heap.Push(minHeap, Pair{first: 1, second: 2})
	heap.Push(minHeap, Pair{first: 3, second: 4})
	heap.Push(minHeap, Pair{first: -1, second: -2})
	heap.Push(minHeap, Pair{first: -100, second: -5})

	fmt.Println("Min Heap:", minHeap)              // Min Heap: &[{-100 -5} {-1 -2} {1 2} {3 4}]
	fmt.Println("Min Heap Length:", minHeap.Len()) // Min Heap Length: 4

	// ℹ️ Note we use the `heap` package instead of the minHeap to pop an item.
	item := heap.Pop(minHeap)
	fmt.Println("Min Item 1: ", item) // Min Item 1:  {-100 -5}

	item = heap.Pop(minHeap)
	fmt.Println("Min Item 2:", item) // Min Item 2: {-1 -2}
	item = heap.Pop(minHeap)
	fmt.Println("Min Item 3:", item) // Min Item 3: {1 2}
	item = heap.Pop(minHeap)
	fmt.Println("Min Item 4:", item) // Min Item 4: {3 4}

	fmt.Println("Min Heap:", minHeap)              // Min Heap: &[]
	fmt.Println("Min Heap Length:", minHeap.Len()) // Min Heap Length: 0
}

type Pair struct {
	first, second int
}

// MinHeap structure initialization
type MinHeap []Pair

// newMinHeap function initializes an instance of MinHeap
func newMinHeap() *MinHeap {
	min := &MinHeap{}
	heap.Init(min)
	return min
}

// Len function returns the length of min heap
func (h MinHeap) Len() int {
	return len(h)
}

// Empty function returns true if empty, false otherwise
func (h MinHeap) Empty() bool {
	return len(h) == 0
}

// Less function compares two elements of the MinHeap given their indexes
func (h MinHeap) Less(i, j int) bool {
	return (h[i].second < h[j].second)
}

// Swap function swaps the values of the elements whose indices are given
func (h MinHeap) Swap(i, j int) {
	h[i], h[j] = h[j], h[i]
}

// Top function returns the element at the top of the MinHeap
func (h MinHeap) Top() Pair {
	return h[0]
}

// Push function pushes an element into the MinHeap
func (h *MinHeap) Push(x interface{}) {
	*h = append(*h, x.(Pair))
}

// Pop function pops the element at the top of the MinHeap
func (h *MinHeap) Pop() interface{} {
	old := *h
	n := len(old)
	x := old[n-1]
	*h = old[0 : n-1]
	return x
}

Execute the code in this playground

Functions

Functions in Go are first-class citizens, this means that you can pass them as arguments to other functions, return them from other functions, and assign them to variables. Here is an example of how to define and use functions in Go:

package main

import "fmt"

// Defining a function.
func add(a, b int) int {
	return a + b
}

func main() {
	fmt.Println(add(10, 20)) // 30
}

Execute the code in this playground

Nested functions

This kind of functions are useful when dealing with graphs and algorithms that uses recursion.

package main

import "fmt"

// Defining nested functions
func outer(a, b string) string {
	c := "c"

	inner := func() string {
		return a + b + c
	}

	return inner()
}

func main() {
	fmt.Println(outer("a", "b")) // "abc"
}

Execute the code in this playground

Struct

Go doesn’t have the concept of class, but we can leverage a struct and functions to mimic this behavior.

package main

import "fmt"

func main() {
	s := NewStudent([]string{"Alice", "Bob", "Martha"})

	s.PrintNames()          // "Alice" \n "Bob" \n "Martha"
	fmt.Println(s.Length()) // 3
}

// Student represents a student in the app.
type Student struct {
	names []string
	size  int
}

// PrintNames prints all the names inside the names slice.
func (s Student) PrintNames() {
	for _, name := range s.names {
		fmt.Println(name)
	}
}

// Length returns the length of the names slice.
func (s Student) Length() int {
	return s.size
}

// NewStudent creates a new `instance` of the Student type.
func NewStudent(names []string) Student {
	return Student{
		names: names,
		size:  len(names),
	}
}

Execute the code in this playground

Happy learning (and interviewing 😆)!