Go Cheatsheet
Below you will find a quick, and short cheatsheet, written with simple examples, and easy to understand words.
Declaring variables of different types:
var i int
var s string
var b byte
var bo bool // Boolean: true or false
var ch rune // Go's equivalent of a single character.
var sSlice []string // This creates a slice of strings.
var sArr [3]string // This creates an array with a length of 3 strings.
var mp map[string]int // Creates a map with string type as key, and int type as value.Note: For the map declared above, when doing write operations, Go panics: panic: assignment to entry in nil map. So an initialization is required before the first write.
Either mp = map[string]int{} or mp = make(map[string]int) will work.
Constants
const n = 20 // Go supports untyped constants.
const num int = 5
const str string = "const"
const st = "this is a string"Declaration/Initialisations that are only valid inside a function.
func declare() { // All functions start with "func" keyword.
str := "" // This way of initialization is only valid inside a function.
num := 3
isTrue := true
c := 'a' // creates a rune
s := []string{} // creates a slice of string type.
hashMap := map[string]int{} // Creates a map of string as key, and int as value.
}Note: In Golang, all declared variables must be used, else compiler will throw an error.
Example of the error: declared and not used: str
Functions
In Golang, functions are declared using the func keyword. Below a and b are parameters with an int type.
func add(a int, b int) int { // int indicates the type of the return value.
// function parameters of the same type can also be written like this:
// func add(a , b int) int {
return a + b
}
func nums() (int, int) { // A way to indicate multiple return values/types.
return 3, 4
}NOTE: Function names that start with lowercase (example: func add()) act like private methods of a package. And function names that start with an uppercase (example: func Add()) act like public methods, and are automatically exported.
Variadic functions
Variadic functions can accept a variable number of arguments. In Go, three dots(…) followed by a type, is used as a parameter to signify a variadic function.
func variadic(nums ...int) {
sum := 0
for _, n := range nums {
sum += n
}
fmt.Println(sum)
}
variadic(3, 4, 5)
variadic(1,2)Defer
An important concept/keyword to understand when learning functions in golang is the concept of “defer”.
defer works quite similar to what it means: it puts off an event to a later time.
defer runs just before the function returns.
func deferExample() {
defer fmt.Println("print this later") // prints this later, when the function is about to return.
fmt.Println("print this now") // prints this first.
}Slice methods
func sliceMethods() {
arr := []int{1, 2, 3, 4}
arr = append(arr, 5) // appending to a slice
val := arr[0] // getting the value at 0th index
arr[2] = 4 // setting the value as 4 at 2nd index of the slice.
// Create a new slice from a specific range of the original slice.
newSlice := arr[0:2] // Here, 0 is start index, 2 is end index. Value at 2nd index is not included.
length := len(newSlice) // len() gives the length of the slice.
}Map methods
func mapMethods() {
mp := map[string]int{}
mp["a"] = 1 // set a key/value
if v, ok := mp["a"]; ok { // checks if the key exists.
// do something with the value.
}
delete(mp, "a") // deletes "a" from the map "mp"
clear(mp) // deletes all entries from the map.
length := len(mp) // Total number of entries in the map.
}Loops
In Golang, loops are done using the for and range keyword
func loops() {
for i := 0; i < 5; i++ {
// do something
}
n := 5
for range n { // range lets you loop over things easily.
// do something
}
// An infinite loop, similar to "while" from other languages.
for {
if n > 5 {
break
}
}
arr := []int{1, 2, 3, 4}
for idx, val := range arr {
// here idx is an index of the current item in arr.
// val is the value at that index.
}
mp := map[string]int{
"a": 0,
"b": 1,
}
for k, v := range mp {
// k: key, v: value
}
}Conditionals:
if/else statements
func conditionals() {
a := 0
b := 1
if a < b {
// do something
} else if b > a {
// do something
} else {
// do something
}
}Switch/case
func cases() {
a := 5
switch a {
case 6:
// do something if a == 6
case 7:
// do something if a == 7
default:
// by default do this, if other cases do not match.
}
}Pointers and references
func pointer() {
num := 4
p := &num
fmt.Println(p) // prints the memory address of num.
}
func changeValue() {
num := 4
passWithoutPointerAndChange(num)
fmt.Println(num) // still prints 4
passWithPointerAndChange(&num) // This changes the value to 5.
// When passed with the pointer, it modifies the value at that address.
fmt.Println(num) // now prints 5
// Same applies to other data types.
arr := []int{}
tryChangingArrWithoutPointer(arr)
fmt.Println(arr) // prints an empty slice [].
tryChangingArrWithPointer(&arr)
fmt.Println(arr) // prints [3].
}
func passWithoutPointerAndChange(num int) {
num = 5 // change is made to the copy, not the original
}
func passWithPointerAndChange(num *int) {
// * is used infront of num to dereference the pointer.
// In simple words, go to the memory address stored in num,
// and give the value that lives there. In this case, 4.
*num = 5 // modifies the value at that address.
}
func tryChangingArrWithoutPointer(arr []int) {
arr = append(arr, 3) // append is used this way in golang.
}
func tryChangingArrWithPointer(arr *[]int) {
*arr = append(*arr, 3) // append is used this way in golang.
}Struct
Struct, in easy words, is a way to create your own type with multiple fields.
type Card struct {
Name string
Number string
CCV int
}
// Initialize Card.
c := &Card{}
// This is a way to create a method for a struct in Golang.
// Go does not have classes.
func (c *Card) Add() {
c.Name = "Rob Kim" // struct fields are accessed using a dot
c.Number = "123456890"
c.CCV = 532
}
// This can be called like this:
c.Add() // Calls Add method on the card created above.Interface
In simple words, an interface is a contract that says: any type that has these methods automatically satifies this interface type. In golang, there is no explicit declaration for interfaces.
Below is a simple “Animal” example to showcase interface:
type Animal interface {
Speak()
}
type Cat struct {
Name string
}
func (c *Cat) Speak() {
fmt.Printf("Meow says %s\n", c.Name)
}
type Dog struct {
Name string
}
func (d *Dog) Speak() {
fmt.Printf("Woof says %s\n", d.Name)
}
func main() {
c := &Cat{
Name: "Zing",
}
d := &Dog{
Name: "Max",
}
animals := []Animal{c, d} // Here, both Cat and Dog have the same method Speak(), which is a method of the Animal interface.
for _, a := range animals { // The types Cat and Dog satisfy the Animal interface type.
a.Speak()
}
}Generics
Generics allows you to write functions or data structures that work with any data type.
Simple example:
func printSlice[T any](s []T) {
for _, v := range s {
fmt.Println(v)
}
}
// Now you can call this generic function printSlice with any data type.
nums := []int{1, 2, 3, 4}
printSlice(nums)
s := []string{"a", "b", "c"}
printSlice(s)Concurrency/Goroutines:
Goroutines are a way to handle concurrent tasks in Go. It allows multiple tasks to be executed at the same time without blocking each other.
func add() {
time.Sleep(2 * time.Second)
fmt.Println("Adding completed")
}
func subs() {
time.Sleep(2 * time.Second)
fmt.Println("Subtracting completed")
}
go add()
go subs()
fmt.Println("I could be doing something else while my goroutines are executing concurrently")
time.Sleep(3 * time.Second) // This is to not let the main function return immediately. NOTE: In a real case, if the above code did not have time.Sleep(3 * time.Second) on the last line, the main function would return immediately after printing the last line,
instead of waiting for all the goroutines to finish. To deal with this, either channels, or a sync package is generally used in real case scenarios.
Channels
In easy to understand words, a channel is like a pipe through which multiple goroutines can share data, without worrying about mutex locks, or deadlocks.
func main() {
ch := make(chan int, 2) // Makes a channel of buffer length 2, that can hold data of type int.
// ch := make(chan int) // You could write this too. This would create a channel without any specific length/size.
go add(5, 4, ch)
go add(2, 5, ch)
res1 := <-ch // receive the value from the channel, and assign it to res1. The program waits here, until it receives the value.
res2 := <-ch // receive the value from the channel
fmt.Println(res1)
fmt.Println(res2)
}
func add(a, b int, ch chan int) {
time.Sleep(2 * time.Second)
ch <- a + b // send the value to the channel
}Mutex
Mutex is a mutual exclusion lock.
Example use case: Mutex is generally used to update a shared slice across multiple goroutines.
Mutex, and other items from the sync package are generally used together. These include: WaitGroup and its methods. This is a way in Go to avoid deadlocks, and race conditions.
A simple example is shown below:
import (
"fmt"
"sync"
"time"
)
type Res struct {
values []int
mu sync.Mutex // To ensure only one goroutine access this resource at a time.
}
func NewRes() *Res {
return &Res{
values: []int{},
}
}
func main() {
var wg sync.WaitGroup // A way to wait for all goroutines to finish their tasks.
r := NewRes()
wg.Add(2) // Add goroutines to the wait group.
go add(5, 4, r, &wg)
go add(1, 3, r, &wg)
wg.Wait() // Wait for all the goroutines to finish their tasks.
for _, v := range r.values {
fmt.Println(v)
}
}
func add(a, b int, r *Res, wg *sync.WaitGroup) {
defer wg.Done() // Mark this goroutine as done, when this function is about to returns.
time.Sleep(2 * time.Second)
sum := a + b
r.mu.Lock() // Lock this resource.
r.values = append(r.values, sum)
r.mu.Unlock() // Unlock this resource.
}