I have been learning and using golang lately so here I am writing down the absolute basic exploration of Golang for anyone who would like to venture out into the ‘Go’ world. Golang is simple, powerful and high performance language. It’s easy to learn and fun to write. It is a combination of modern programming languages with the speed and reliability of low-level languages, making it a top choice for building robust and scalable applications.

All code snippets provided in this article can be run on a Go playground like here, if you wish to setup go in your local system checkout go.dev.

Hello world in Go

A simple hello world in Go looks like this.

1
2
3
4
5
6
7
8
package main

import "fmt"

func main() {
	fmt.Println("Hello World!")
}

main is the entrypoint function for any go program.
import "fmt" imports the ‘fmt’ package.
fmt.Println() is an exported function from the ‘fmt’ package.
In go, the uppercase start of the function name, represent a publicly accesible function in that package.

Data types

In go the variable name is followed by its data type.

There are data types for string, number and boolean in Go. bool is used for boolean, int is for integer and uint for unsigned integers . Float for floating point numbers.

The primitive data types consists of bool, string, numeric types that include Integer(int8,int16,int32,int64, uint, uint8, uint16, uint32, uint64), Floating point numbers(float32 and float64) and complex numbers (complex64 & complex128).

1
2
3
4
5
6
//  data types
var a bool = true // Boolean
var b int = 5   // int is simply alias for 32 or 64 bit integer, depends on the machine architecture
var c float32 = 3.14  // Floating point numbers
var d string = "Hi!"  // String

Zero values is default value assigned to declared variables that aren’t initialized. Zero value for numeric types is 0, for bool type is false and for strings is empty string “”.

Variable Declaration & Initialization

There are multiple ways to declare and initialize a variable. Check the below code snippet on how you can declare a variable and initialize it. The := operator (known as walrus operator) is used for type inference so that the type can be infered from the value defined Constant values can be defined with a const keyword.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21

	
	var myvar2 string //declare
	myvar2 = "Java" //initialize

	var myvar1 = "Golang" //declare and initialize

	//using a walrus operator
	myvar3 := "Python"

	//constant variables
	const count = 5
	const text string = "Hello world!"

	


	fmt.Println("myvar1 = ", myvar1, ",myvar2 = ", myvar2, ",myvar3 = ", myvar3, "constant var ", count)

	//Output: myvar1 =  Golang ,myvar2 =  Java ,myvar3 =  Python count  5

Common data structures: arrays, slices, maps..

Arrays

An array type definition specifies a length and an element type. var a [5]int specifies an array of integers of length 4.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package main

import "fmt"

func main() {

	//  a array of length 5
	var a [5]int
	a[0] = 1

	fmt.Println(" array a ", a)

	// another way to define an array
	b := [6]string{"a", "b"}

	fmt.Println(" array b ", b)

	var twoD [3][3]int
	for i := 0; i < 3; i++ {
		for j := 0; j < 3; j++ {
			twoD[i][j] = i + j
		}
	}

	fmt.Println(" twoD array", twoD)

	/* output

		array a  [1 0 0 0 0]
		array b  [a b    ]
		twoD array [[0 1 2] [1 2 3] [2 3 4]]

	*/
}

Slices

Slice is an abstraction on arrays unlike arrays it is more flexible and dynamically-sized.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

package main

import "fmt"

func main() {

// slices are alternatives to array
	vowels := []string{"a", "e", "i", "o", "u"}

	c := make([]int, 5)

	// append to an existing slice
	c[0] = 1
	c[1] = 1
	c[2] = 2
	c[3] = 3
	c[4] = 5
	c = append(c, 8)

	fmt.Println(" slice c ", c)
	fmt.Println(" slice c ", c[2:4])

	// 2d slice
	twoDS := make([][]int, 3)
	for i := 0; i < 3; i++ {
		twoDS[i] = make([]int, 3)
		for j := 0; j < 3; j++ {
			twoDS[i][j] = i + j
		}
	}

	fmt.Println(" twoD slices ", twoDS)
	fmt.Println(" twoD slices ", twoDS[0:2][1:3])


	/* output
	 slice c  [1 1 2 3 5 8]
	 slice c  [2 3]
	 twoD slices  [[0 1 2] [1 2 3] [2 3 4]]
	 twoD slices  [[1 2 3] [2 3 4]]


	*/
}

Maps

Map is a data structure that stores collection of key-value pairs. The main operations on map includes setting a key-value, retrieving a value from key and deleting a key.

If a key doesn’t exists it’s zero value is returned.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main

import "fmt"

func main() {

	m := make(map[string]string)

	m["key1"] = "value1"
	m["key3"] = "value"

	// second parameter denotes whether key is present or not
	val1, _ := m["key1"]
	fmt.Println("value of key1 = ", val1)
	fmt.Println("value of key3 = ", m["key3"])

	val, exists := m["key2"]
	fmt.Println("value of key2 exists? ", exists, "value ", val)

	delete(m,"key3") //deletes key3 from map

	/* output

		value of key1 =  value1
		value of key3 =  value
		value of key2 exists?  false value  

	*/

}



if-else, switch, and for loops

if-else statement

In an if statement enclosing brackets around the condition is not necessary in Go.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11

const value = 101

	if value%10 == 0 {
		fmt.Println("Value is divisible by 10")
	} else {
		fmt.Println("Value is NOT divisible by 10")
	}

	// Output: Value is NOT divisible by 10

Variables can also be declared and initialized in an if-else statement.

1
2
3
4
5
6
7

	if value := 101; value%10 == 0 {
		fmt.Println("Value is divisible by 10")
	} else {
		fmt.Println("Value is NOT divisible by 10")
	}

Switch

Switch in go doesn’t require a break on each case, it is implicitly provided in each case. The values could be any other data type and need not be ca onstant or integer.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
const value = 101


	switch value {
		case 99:
			fmt.Println("Value is 99")

		case 100:
			fmt.Println("Value is 100")

		case 101:
			fmt.Println("Value is 101")

		default:
			fmt.Println("Could not determine value")
	}

	// Output: 	Value is 101


Trickier part is switch without a condition. In such scenario, the case must be boolean and the first case that evaluates to true is executed. It can be helpful when we have a long chain of if else statements.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13

	text := "Hello World"

	switch {

	case text == "Bye":
		fmt.Println("Text is Bye")
	case text == "Hello World":
		fmt.Println("Text is Hello World")
	default:
		fmt.Println("Text doesn't match")
	}

Loops

Below is a for loop to sum elements from 1 to 10.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10

	sum := 0

	for i := 1; i <= 10; i++ {
		sum += i
	}
	fmt.Println(sum)

	// Output: 55

Go’s way to while loops is different than that in other languages like Java or C++.

1
2
3
4
5
6
7
8
	k := 1 

	//while version of Go

	for k<=100 {
		k += 10
	}

An infinite loop

1
2
3
4
5

	for {
		// infinite loop, keep going
	}

Functions

Functions can be defined like this func <func-name> (parameters) return-type. Go functions can accept and return multiple values. Below code show usage of functions add accepts two values and returns sum of the values. We can also omit type when we have consecutive parameter of same type up to the final parameter like func add(a , b int) int

swap function accepts two arguments and returns two values

sum accepts any number of arguments of int type and returns a int value.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package main

import "fmt"

func add(a int, b int) int {
	return a + b
}

func swap(a int, b int) (int, int) {
	return b, a
}

func sum(values ...int) int {
	sum := 0
	for i := 0; i < len(values); i++ {
		sum += values[i]
	}
	return sum
}

func main() {

	fmt.Println(add(10, 11))

	a, b := swap(10, 11)

	fmt.Println(a, b)

	fmt.Println(sum(10, 11, 12, 13, 14, 15))

	/*Output:
	21
	11 10
	75
	*/
}

Pointers

Pointers stores memory address of a value.

*int denotes a pointer holding integer value.

* operator denotes the value the address holds. & operator denotes the address of the operand.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15

var ptr *int; //declaration 

mynum := 23

ptr = &mynum // ptr stores address of mynum

*ptr = *ptr+1 // operation on the ptr

fmt.Println("ptr value ", ptr)
fmt.Println("*ptr value ", *ptr)

// Output
// ptr value  0xc00001c030
// *ptr value  24

While passing a pointer to a function, we pass the reference to the value this allows us to change the value on other function.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import "fmt"

func swap(ptr1 *int, ptr2 *int) {
	var c int = *ptr1
	*ptr1 = *ptr2
	*ptr2 = c
}

func main() {

	a := 120
	b := 300

	swap(&a, &b)

	fmt.Println(a, b)

	/*Output:
	300 120
	*/
}

Structs

Struct is a collection of fields, they are helpful to create our own custom data type.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package main

import "fmt"

type User struct {
	name   string
	age    int
	active bool
}

func main() {

	p := User{"User1", 15, false}
	p.age = 20

	fmt.Println(p.age)

	u := User{"User2", 25, true}

	u.deactivate()

	fmt.Println(u)

}

func (u *User) deactivate() {
	(*u).active = false
}
func (u *User) activate() {
	(*u).active = true
}

/* Output
20
{User2 25 false}
*/

Thing to note is while updating a struct inside other function one must pass it as a reference to the function otherwise the copy of the struct is updated which doesn’t update the original struct value. The below functions wouldn’t work as expected as the user is being passed as a value.

1
2
3
4
5
6
func (u User) deactivate() {
	u.active = false
}
func (u User) activate() {
	u.active = true
}

Error Handling

Error handling is all together different in go. There is no stack trace and no try/catch mechanism, instead errors are just some value of type error and are handled conditionally.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

package main

import (
	"errors"
	"fmt"
)

func divide(a int, b int) (int, error) {
	if b == 0 {
		return -1, errors.New("Can't divide by 0")
	}
	return a / b, nil
}

func main() {
	out, err := divide(10, 0)

	if err != nil {
		fmt.Println("There is some error")
	} else {
		fmt.Println("The answer is ", out)
	}
}

//Output: There is some error

There are few more topics like interfaces, generics, concurrency etc. that I have not included in this post to keep it simple and beginner friendly. Some helpful resources to delve into more advanced topics: