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 asdict
,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 😆)!