Hello everyone 👋, Today I have something interesting for you.

I was recently contributing to Porter - a CNCF sandbox project and Once I fixed the issue and was done with the pull request, I was motivated to make more contributions to the project. And, since the project was mainly written in Golang, I thought this is a good opportunity to learn Golang.

While learning Golang myself, I made some notes which are more than sufficient to get familiar with the syntax and necessary concepts of Golang. So, Let’s get started.

History of Golang #  

How is memory allocated in Golang? #  

When Go starts a program, It requests a block of memory from operating system, cuts it into small chunks and manages it itself.

The basic unit of Go memory management is the mspan, which consists of several pages, each mspan can allocate a specific size of object.

Go’s memory allocator allocates objects in three categories based on their size:

Data types in Golang #  

Go has a concept of types that is either explicitly declared or implicitly inferred.

It is a fast, statically typed, compiled language that feels like dynamically typed, interpreted language.

Variables #  

Now, Let’s talk about how we can use variables inside Golang.

Variables are declared using var keyword followed by variable_name and its type such as int, string etc.

1// The syntax for declaring a variable is :
2// var <variable_name> <data_type>
3var str string
4
5// After declaring we can initialise variables like this
6
7str = "Hello World!"

Declaring + Initialising Variables #  

1// The syntax for declaring and initialising a variable is :
2// var <variable_name> <data_type> = <value>
3
4var str string = "Hello"
5var num int = 10
6var b bool = true
7var decimal float64 = 69.420
1// Variables of same data type can be declared together like this
2var f,b string = "foo","bar"
3
4// Variables of different data type can also be declared together like this
5var (
6    str string = "foo"
7    num int = 10
8)

We can also use the walrus operator := to declare and initialise variables. We do not explicitly mention type of variable, It is implicitly assigned a type by the compiler.

1str := "foo"

Constants #  

Constants are variables whose value is initialised once and can never be changed again. They are untyped unless explicitly given a type at the time of declaration, which allows flexibility.

Constants need to be initialised at the time of declaration as the concept of zero value doesn’t apply to constants. And, they can not be initialised using the := operator.

1// const <const_name> <data_type> = value
2
3const PI float64 = 3.14
4
5// const <const_name> = value
6
7const PI = 3.14

Printing Variables #  

Variables can be printed to console by using methods provided by fmt package such as Print,Printf,Println etc. Code to print a variable in go :

 1package main
 2import "fmt"
 3
 4func main() {
 5  var hello string = "Hello"
 6  var money float64 = 69.420
 7
 8  fmt.Print(str , " World")
 9  fmt.Println(str , " World")
10  // Difference between Print and Println is that Print method doesn't add
11  // newline after printing a statement while Println does.
12
13  fmt.Printf("%s World! I have $ %f in my account.", hello, money);
14  // Printf stands for Print Formatter and it takes a template string with
15  // format specifiers and Object args(s).
16}

Printf Format specifiers #  

Format specifiers are prefixed with a % symbol. There are different types of format specifiers for printing different types of variables.

VerbDescription
%vdefault format
%Ttype of the value
%dintegers
%ccharacter
%qquoted characters/string
%splain string
%ttrue or false
%ffloating numbers
%0.2ffloating numbers upto 2 decimal places

Variable Scope #  

Scopes are defined using Blocks which are declared using braces { }.

1{
2  //outer block
3  {
4    //inner block
5  }
6}

Inner block can access variables declared in Outer block but vice versa is not true. e.g:-

 1func main() {
 2  city := "Tokyo"
 3  {
 4    country := "Japan"
 5    fmt.Println(country)
 6    fmt.Println(city)
 7  }
 8  // fmt.Println(country) // this line will give error as Outer block cannot
 9  // access variable `country` defined in inner block
10  fmt.Println(city)
11}

Local vs Global Variables #  

Local variables are declared inside a function or a block, also inside loops or conditional statements. These are only available inside and are not accessible outside the function or block.

1package main
2func main() {
3  name := "Zoro" // `name` is a local variable only available inside main func
4}

Global variables are declared outside of a function/block and are available throughout the lifetime of a program. These variables are declared at the top of the program outside of any function or block and cannot be declared using the walrus := operator, these have to be declared in the standard way.

1package main
2import "fmt"
3
4var name string = "Zoro"
5// `name` is a global variable available for use anywhere in the program
6
7func main() {
8  fmt.Println(name)
9}

Concept of Zero Value #  

When you declare a variable and do not initialise it, It is given a default value by the compiler known as zero value which is different for different data types. e.g:-

Data TypesZero values
booltrue
int0
float640.0
string""
etc.

How to take user input? #  

fmt package provides a function Scanf which is used to take input from user.

More on Types #  

Checking Types #  

We can check data types of variable using %T or reflect.TypeOf provided by reflect package.

Type Casting/Conversion #  

The process of converting one data type to another is called Type Casting or Conversion. However, It does not guarantee that the value will remain intact.

1package main
2import "fmt"
3
4func main() {
5    var f float64 = 69.420
6    var i int = int(f)
7    fmt.Println("Integer Value: ",i)
8}
1❯ go run main.go
2Integer Value: 69

Here the decimal value got truncated while converting float to integer.

Type Conversion can also be done by methods provided by a package strconv

Conditionals #  

If-else and else if #  

1if condition_1 { // parenthesis () around the condition is optional
2    // execute when condition_1 is true
3} else if condition_2 { // else should start from the same line where if block ends
4    // execute when condition_2 is true
5} else {
6    // if none of the condition is true
7}

Switch Case #  

The expression doesn’t need to be in parenthesis and break; statement is also not needed.

1switch expression {
2  case value_1:
3    // execute when expression equals to value_1
4  case value_2, value_3:
5    // execute when expression equals to value_2 or value_3
6  default:
7    // execute when no matches found
8}

the fallthrough keyword #  

It is used to force the execution flow of switch case.

 1switch expression {
 2  case value_1:
 3    // execute when expression equals to value_1
 4  case value_2:
 5    // execute when expression equals to value_2
 6    fallthrough // It forces to execute the successive(next) case block
 7  case value_3:
 8    // execute when expression equals to value_3 or value_2 since fallthrough is
 9    // used
10  default:
11    // execute when no matches found
12}

Switch with conditions #  

We don’t give expression to switch statement and instead we can give conditions for each cases.

1switch {
2  case condition_1:
3    // execute when condition_1 is true
4  case condition_2:
5    // execute when condition_2 is true
6  default:
7    // execute when no conditions are true
8}

Looping in Go #  

There is only one loop in Go that is for loop.

1for initialisation; condition; post {
2  // statement(s)
3}

First initialisation happens, then if condition is true, statements inside loop are executed and after execution is completed, post statement (mostly increment of decrement) is executed.

Arrays in Go #  

An Array is a collection of similar data elements stored at contiguous memory locations.

In Golang, Arrays are of fixed length, And since, we have pointers in Go, we can get the address of any element of array.

 1// Array Declaration
 2// var <array_name> [<size_of_array>] <data_type>
 3
 4var num [5]int
 5
 6// Array Declaration and Initialisation
 7// var <array_name>[<size_of_array>]<data_type> = [<size_of_array>]<data_type>{<values>}
 8
 9var num [5]int = [3]int{6,9,4,2,0}
10
11// Shorthand
12// <array_name> := [<size_of_array>]<data_type>{<values>}
13
14num := [3]int{6,9,4,2,0}
15
16// Shorthand using ellipsis(...)
17// <array_name> := [...]<data_type>{<values>}
18
19num := [...]int{6,9,4,2,0}

Length of Array #  

To calculate length of an Array, we can use builtin len() function.

1fruits := [4]string { "this", "is", "a", "test" }
2fmt.Println(len(fruits))

Looping through Array #  

1arr := [3]int {6,9,4,2,0}
2
3for i := 0; i < len(arr[i]); i++ {
4  fmt.Println(arr[i]);
5}
 1arr := [3]int {6,9,4,2,0}
 2
 3for index, element := range arr {
 4  fmt.Println(index, "->", element)
 5}
 6
 7// If we don't need index, we can replace it with underscore `_`
 8for _, element := range arr {
 9  fmt.Println(element)
10}

MultiDimensional Array #  

1// 2D Array
2arr := [3][2]int {{1, 2}, {3, 4}, {5, 6}}
3
4fmt.Println(arr[2][1]) // This will print 6

Slices #  

A Slice is a continous segment of an underlying array, which offer more flexibility as they are variable typed (elements can be added or removed).

Slices have three major components

Creating a Slice #  

1// <slice_name> := []<data_type>{<values>}
2grades := []int{10, 20, 30}

Creating slice from an array from start_index till the end_index (end_index is not included).

1// array[start_index : end_index]
2arr := [8]int{2, 3, 4, 5, 6, 7, 8, 9}
3
4arr[1:5] // [3, 4, 5, 6]
5// array -> | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
6//                ^
7//                Slice Pointer
8// slice -> | 3 | 4 | 5 | 6 | -> length = 4, capacity = 7

If we omit the start_index, it will be 0 by default and if we omit the end_index, it will be the length of array by default.

1// array[start_index : end_index]
2arr := [5]int{11, 12, 13, 14, 15}
3
4arr[ :3] // [11, 12, 13]
5arr[2: ] // [13, 14, 15]
6arr[ : ] // [11, 12, 13, 14, 15]

Using make function

1// slice := make([]<data_type>, length, capacity)
2slice := make([]int, 5, 8)

Since Slices are references to the original array, If we mutate the slice it will aslo mutate the original array from which the slice was created.

1arr := [5]int{11, 12, 13, 14, 15}
2
3slice = arr[ :3] // [11, 12, 13]
4slice[1] = 1000
5
6// Now the slice will become, slice - [11, 1000, 13]
7// And, the original array, arr - [11, 1000, 13, 14, 15]

Appending to Slice #  

append function is used to append a slice or values of the same type as that of the slice to the slice.

If the number of total values after appending become more than the capacity of the slice, then the slice’s capacity will be doubled.

Deleting from Slice #  

To delete an element from a slice, we can just create a new slice which doesn’t contain the element to be deleted.

 1arr := [5]int {10, 20, 30, 40, 50}
 2
 3i := 2 // index of element to be deleted
 4
 5slice_1 := arr[:i] // [10, 20]
 6slice_2 := arr[i+1:] // [40, 50]
 7
 8new_slice := append(slice_1, slice_2...)
 9// Now new_slice consists of [10, 20, 40, 50] and the element at index 2 got
10// deleted

Copy from a Slice #  

1// func copy(dst, src []Type) int
2// num := copy(dest_slice, src_slice)
3
4src_slice := []int{10, 20, 30, 40, 50}
5dest_slice := make([]int, 3)
6
7num := copy(dest_slice, src_slice)

Maps in Go #  

Maps are unordered collection of key-value pairs, implemented using hash tables.

1// var <map_name> map[<key_data_type>]<value_data_type>
2var my_map map[string]int // creates a nil map
3
4// <map_name> := map[<key_data_type>]<value_data_type>{<key_value_pairs>}
5my_map := map[string]string{"h": "ello", "w": "orld"}
6
7// <map_name> := make(map[<key_data_type>]<value_data_type>, <initial_capacity>)
8my_map := make(map[string]int)

Access Items of Map #  

1my_map := map[string]string{"h": "ello", "w": "orld"}
2
3fmt.Println(my_map["h"]) // Output : ello
1// value, found := map_name[key]
2my_map := map[string]string{"h": "ello", "w": "orld"}
3
4value, found := my_map["h"]
5fmt.Println(found, value) // Output: true, ello

Adding/Deleting a key-value pair #  

1my_map := map[string]string{"h": "ello", "w": "orld"}
2
3my_map["t"] = "est" // adds a key-value pair
4
5fmt.Println(my_map) // Output: map[h:ello w:orld t:est]
6
7delete(my_map, "w") // deletes a key-value pair
8
9fmt.Println(my_map) // Output: map[h:ello t:est]

Clearing/Truncating a map #  

Items can be removed one by one using a for loop or Reinitializing the map can also clear all items from the map.

1my_map := map[string]string{"h": "ello", "w": "orld"}
2my_map := make(map[string]string) // reinitializing with make truncates the map

Functions #  

Functions are self-contained units of code which do a certain task, and help us divide a program into organisable and reusable chunks.

 1// func <function_name>(<params>) <return_type> {
 2//   function body
 3// }
 4
 5func addNumbers(a int, b int) int {
 6  sum := a + b
 7  return sum;
 8}
 9
10addNumbers(10, 20)

Return Type - A function can or can not have return value(s).

 1// standard return values
 2func operation(a int, b int) (int, int) {
 3  sum := a + b
 4  diff := a - b
 5  return sum, diff
 6}
 7
 8// named return values
 9func operation(a int, b int) (sum int, diff int) {
10  sum := a + b
11  diff := a - b
12  return
13}
14
15func main() {
16  sum, difference := operation(20, 10)
17  fmt.Println(sum, " ", difference)
18}

Variadic functions #  

Variadic functions have variable number of parameters.

1// func <function_name>(param1 type, param2 type, param3 ...type) <return type>
2
3func sumNumbers(numbers ...int) int
4
5func sumNumbers(str string, numbers ...int) int

The Variadic parameter numbers is a slice of all the arguments passed to the function.

 1func printDetails(student string, subjects ...string) string {
 2  fmt.Println("hi ", student, " /n Here are your subjects - ")
 3  for _, sub := range subjects {
 4    fmt.Println(sub, " ")
 5  }
 6  return sum
 7}
 8
 9func main() {
10  fmt.Println(printDetails("Rohan", "English"))
11  fmt.Println(printDetails("Sohan", "Mathematics", "English", "Science"))
12}

Anonymous function #  

Anonymous functions are functions declared without any named indentifier to refer to it.

1func main() {
2  x := func(l int, b int) int { // Anonymous function
3    return l * b
4  }
5  fmt.Printf("%T , %v", x, x(20, 50))
6}
7
8// Output: func(int, int) int, 1000

Defer Statement - A defer statement delays the execution of a function until the surrounding function returns.

 1func printString(str string) {
 2  fmt.Println(str)
 3}
 4func printNumber(int num) {
 5  fmt.Println(num)
 6}
 7
 8func main() {
 9  defer printNumber(101) // delays the function execution
10  printString("GoLang")
11}
12
13// Output:
14// GoLang
15// 101

Pointers #  

A Pointer is a variable that holds the memory address of another variable. It points to the address where the memory is allocated and provides ways to find or even change the value located at that address.

Address and Dereference operator #  

 1// Declaring a Pointer
 2// var <pointer_name> *<data_type>
 3var ptr_i *int
 4var ptr_s *string
 5
 6// Initialising a Pointer
 7// var <pointer_name> *<data_type> = &<variable_name>
 8i := 10
 9var ptr_i *int = &i // ptr is a pointer which holds the address of variable i
10
11// Shorthand
12s := "hello"
13ptr_s = &s

We can get or modify the value of a variable using * operator on a pointer that holds the address of that variable.

 1s := "hello"
 2ps := &s
 3
 4fmt.Println(s, *ps)
 5
 6*ps := "world"
 7
 8fmt.Println(s, *ps)
 9
10// Output:
11// hello hello
12// world world

Passing by value #  

Function is called directly passing the value of variable as argument, so modifying the variable inside the function doesn’t affect the original variable as the parameter is copied into another location of memory.

All basic data types such as int, bool, string etc are Pass by value by default.

 1func modify(n int) {
 2  n += 10
 3}
 4
 5func main() {
 6  num := 15
 7  fmt.Println(num)
 8  modify(num)
 9  fmt.Println(num)
10}
11
12// Output:
13// 15
14// 15

Passing by Reference #  

By using pointers, we can pass the references of variables as arguments and when their value is modified the value of original variable is also modified as the memory address of the original variable is passed using pointer.

Slices are references to an underlying Array so they are Pass by reference, Maps are also Pass by reference by default.

 1func modify(n *int) {
 2  *n += 10
 3}
 4
 5func main() {
 6  num := 15
 7  fmt.Println(num)
 8  modify(&num)
 9  fmt.Println(num)
10}
11
12// Output:
13// 15
14// 25

Structs #  

Structs are used to create custom data types by grouping various data elements together and referencing them through a single variable name.

Note: Structs are also Pass by value by default, so to modfiy values on Struct use pointer to follow Pass by reference.

Declaration and Initialisation #  

 1// Declaration
 2// type <struct_name> struct {
 3//   list of fields
 4// }
 5
 6type Student struct {
 7  name string
 8  rollNo int
 9  marks []int
10  grades map[string]int
11}
12
13// Initialising
14// var <variable_name> <struct_name>
15var s Strudent
16
17// Initialising using new keyword
18// <variable_name> := new(<struct_name>)
19st := new(Strudent)
20
21st := Student{
22  name: "Rohan",
23  rollNo: 10,
24  marks: {80, 74, 96}
25}

Accessing Fields #  

 1// <variable_name>.<field_name>
 2
 3type Student struct {
 4  name string
 5  rollNo int
 6  marks []int
 7  grades map[string]int
 8}
 9
10var st Student
11
12st.name = "Sohan"
13fmt.Println(st.name, st.rollNo)

Equality : Two structs will be equal only if they are created from same Struct definition and they also have same values.

Method #  

A Method is a function that has a defined reciever (extra parameter after func keyword).

1func (s Student) getMarks() float64 {
2  // code
3}
4func (s *Student) getMarks() float64 {
5  // code
6}

Example:

 1type Circle struct {
 2  radius float64
 3  area float64
 4}
 5
 6func (c *Circle) calcArea() {
 7  c.area = 3.14 * c.radius * c.radius
 8}
 9
10func main() {
11  c := Circle{radius: 5}
12  c.calcArea()
13  fmt.Printf("%+v", c)
14}
15
16// Output: {radius:5 area:78.5}

Method sets #  

A set of methods that are available to a data type and are useful to encapsulate functionality.

 1type Student struct {
 2  name string
 3  grades []int
 4}
 5
 6func (s *Student) displayName() {
 7  fmt.Println(s.name)
 8}
 9
10func (s *Student) calculatePercentage() float64 {
11  sum := 0
12  for _, v := range s.grades {
13    sum += v
14  }
15  return float64(sum*100) / float64(len(s.grades)*100)
16}
17
18func main() {
19  s := Student{name: "Rohan", grades: []int{90, 75, 84}}
20  s.displayName()
21  fmt.Printf("%.2f%%", s.calculatePercentage())
22}

Interfaces #  

An interface is like a blueprint for a method set. It specifies a method set and is a powerful way to introduce modularity in Go. It describes all the methods of a method set by providing the function signature for each method but does not implement them.

1// type <interface_name> interface {
2//  method signatures
3// }
4
5type FixedDeposit interface {
6  getRateOfInterest() float64
7  calcReturn() float64
8}

Implementing Interface #  

 1type shape interface {
 2  area() float64
 3  perimeter() float64
 4}
 5
 6type square struct {
 7  side float64
 8}
 9
10func (s square) area() float64 {
11  return s.side * s.side
12}
13
14func (s square) perimeter() float64 {
15  return 4 * s.side
16}
17
18type rect struct {
19  length, breadth float64
20}
21
22func (r rect) area() float64 {
23  return r.length * r.breadth
24}
25
26func (r rect) perimeter() float64 {
27  return 2*(r.length+r.breadth)
28}
29
30func printData(s shape) {
31  fmt.Println(s)
32  fmt.Println(s.area())
33  fmt.Println(s.perimeter())
34}
35
36func main() {
37  r := rect{length: 3, breadth: 4}
38  s := square{side: 5}
39  printData(r)
40  printData(c)
41}

Final Remarks #  

So, These were the important concepts of Golang, And I know this was a lot but if you made it till here, then Congrats 🥳.

If you liked this blog, found any mistakes or just want to say Hi, feel free to send an email or personal message me on X/Twitter.

Now, I will see you in the next blog. Until then keep learning and take care, Bye.