Basics
Notes on the GO Programming language
Updated: 25 June 2025
Installation
Follow the installation on the Go Docs for your operating system
Once done with that, you will need to add ~/go/bin
to your PATH
Hello World
First, create a new directory for your project. In it create a module to enable dependency tracking - you can do this with:
1go mod init example/hello
Make a new project directory with a file called main.go
with the following content
Go has a main
function that needs to be created for every package and it will be run automatically. We also need to import fmt
to print content to the screen. Also note that Go strings must have double quotes "hello"
or we will get an error
1package main2
3import "fmt"4
5func main() {6 fmt.Println("Hello World")7}
We can run this with the following command from the project dir
1go run main.go
Or since it’s a module, we can just do:
1go run .
Build and Run
We can build binaries for an application with go install
which can then be executed from the terminal
The simplest way to build a program is using go build
which will build it and output it in the current directory
For example, we can build and run it as follows:
1go build2
3./hello
You can also install the package globally with:
1go install
Which will add this to ~/go/bin
and can just be run using:
1hello
Variables and Data Types
Go has the following data types
- string
- bool
- int int8 int16 int32 int64
- uint uint8 uint16 uint32 uint64
- byte (uint8)
- rune (int32)
- float32 float64
- complex64 complex128
There are a few different ways to create variables, note that if we create a variable and do not use it we will get an error
Var
1var name string = "John"
Or with a separate declaration and initialization:
1var age int2
3age = 5
Type Inference
1var name = "John"
Get Type
We can make use of the following function to get the type of a variable
1fmt.Printf("%T",name)
Constants
We can define constants with the const
keyword
1const name = "John"
Global Variables
We can declare global variables by defining them outside of the main function
Shorthand Method
Inside of a function we can declare variables using the assignment operator with the following without having to specify the type of var
keyword
1name := "John
Multiple Assignments
We can do multiple assignments as follows
1name, age := "John", 15
Booleans
Booleans in Go a represented using a bool
type and are either true
or false
1var online = true2
3var active bool
Booleans are implicily initialized as
false
The boolean operators are:
&&
- and||
- or!
- not
Numbers
Go has basic numeric types for representing integers and floating point values.
Some of these are int
, float64
, and uint
and conversion between types can be done using functions with the name of the respective type. For example:
1var x int = 52var y = float64()
Go supports the normal numerical operations such as +
, -
, *
, /
, and %
. Note that for integer division the number is truncated back to an int
Numeric operations are only supported between numbers of the same type
Strings
string
is an immutable sequence of bytes and can be defined using double quotes:
1var str1 = "Hello"2str2 := "World"
Concatenation of strings can be done using +
like so:
1str3 := str1 + " " + str2
The strings
package also has many methods for working with strings:
1import "strings"2
3func caps(name string) {4 strings.ToUpper(name)5}
String Formatting
String formatting can be done using the fmt
package which can format strings using fmt.Sprintf
like so:
1str := fmt.Sprintf("int %d, int %03d, float %.3f, string %s, props %+v", 5, 7, 1.5, "hello", myStruct)2// str == "int 5, int 007, float 1.500, string hello, props { Name "hello" }"
Lots of other formatting options also exist for string formatting and can be found in the docs
Packages
Importing Packages
We can import multiple packages by declaring each package in the import
statement on a different line
1import (2 "fmt"3 "math"4)
Dependencies
We can add dependencies to our Go module by simply importing them in the code that needs it, for example:
1import "rsc.io/quote"
And then Go can automatically add it to our go.mod
with:
1go mod tidy
Creating Packages
Create a new folder and in it you can define the package name and some functions in it
1package mypackage2
3func myfunction() {4 ...5}
And then import the package by referring to its path in the import function
Functions
Functions are defined with the func
keyword, the function name, inputs, and return types and values
1func greet(name string) string {2 return "Hello " + name3}
The convention is to start functions with an uppercase letter if they’re public and lowercase if they’re private
Functions can also have multiple input parameters. If these are of the same type the type can just be specified once:
1go canMessage(visible, online bool) bool {2 return visible && online3}
Variadic Functions
A variadic function is a function that takes a variable number of arguments, this is done using ...
in the type of the function:
1func formatMany(format string, strs ...string) string {2 result := ""3
4 for _, str := range strs {5 result += " " + fmt.Sprintf(format, str)6 }7
8 return result9}10
11func main() {12 str := formatMany("Hello %s", "John", "Alice", "Bob")13 fmt.Println(str)14}
The variadic parameter must be the last in the parameter list
A slice can also be passed into a variadic function using ...
after the name of the variable:
1func main() {2 names := []string{"John", "Alice", "Bob"}3 str := formatMany("Hello %s", names...)4}
Arrays and Slices
In Go arrays are fixed length, and a slice is an array without a fixed size
Arrays
Arrays are fixed in size and can be defined with
1var myArray [3]string2
3myArray[0] = "Hello"4myArray[2] = "Word"
Or with initial values
1myArr := [2]string{"Hello", "World"}
Slices
A slice is essentially an array that does not have a fixed size
1mySlice := []string{"Hello", "World","!"}
We can also make slices by using the same notation as other languages
1newSlice := mySlice[2:5]
Elements can be added to a slice using the append
function:
1mySlice := []int{1,2,3}2mySlice = append(mySlice, 4,5,6)
Note that
append
is not a pure function and the original slice may be modified
Conditionals
Conditionals do not require parenthesis, however they can be used
If Else
1if x < y {2 ...3} else if x == y {4 ...5} else {6 ...7}
Swtich/Case
1switch x {2 case: 5:3 ...4 case 10:5 ...6 default:7 ...8}
Loops
There are two methods for building for loops
1i := 12for i <= 01 {3 ...4 i++5}
1for i := 1; i <= 10; i++ {2 ...3}
Maps
Maps are key-value pairs and can be defined and accessed with
1ages := make(map[string]int)2
3ages["Bob"] = 354ages["John"] = 5
We can delete a value from a map with the delete
function
1delete(emails, "Bob")
We can also declare a map with initial values
1emails := map[string]int{"Bob":35, "John":5}
Range
Range is used for looping through values
1ids := []int{1,2,3}2
3for i, id := range ids{4 ...5}
If we are not using the i
variable, we can use an _
to receive any inputs that we will not use
1for _, id := range ids{2 ...3}
1emails := map[string]int{"Bob":35, "John":5}2
3for k, v := range emails {4 ...5}
Pointers
A pointer allows us to point to the memory address of a specific value, we can get the pointer for a value with the &
sign
1a := 52b := &a
If we wan to get back from the pointer to the actual value we can do that with the *
operator
1a == *b // true2a == *&a //true
We can modify the value of a pointer
1*b = 102a == 10 // true
The reason to use pointers can be more efficient when passing values
Closures
We can define anonymous functions to declare anonymous functions that can be used as closures
1func adder () func (int) int {2 sum := 03 return func(x int) int {4 sum += x5 return sum6 }7}8
9
10func main() {11 sum := adder()12 for i:= 0; i < 10; i++ {13 fmt.Println(sum(i)) // 0 1 3 6 ...14 }15}
Structs
Structs are like classes
Structs can contain values and functions, of which we can have value reveivers and pointer receivers. Value receivers just do calculations, Pointer Receivers modify data
1type Person struct {2 firstName string3 lastName string4 age int5}
Structs can be instantiated using all the property names or using the values provided in the struct order:
1person1 := Person{firstName: "John", lastName: "Smith", age: 25}2
3person2 := Person{"John", "Smith", 25}
new
struct
In Go we can also create an instance of a struct where each value has the default value using the new
function:
1// possible but not recommended as the values are zero-initialized so relatively meaningless2person := new(Person)
Methods
These are functions that can be called on a struct directly using a Receiver argument that is defined after the func
keyword:
1func (p Person) myValueReceiver() string {2 return "Hello " + p.firstName3}4
5func (p *Person) myPointerReceiver() {6 p.age++7}
They can then be called using the syntax as seen below
1func main() {2 person := Person{firstName: "John", lastName: "Smith", age: 25}3 person2 := Person{"John", "Smith", 25}4
5 person.firstName // John6
7 person.myValueReceiver() // Hello John8 person.myPointerReceiver() // person.age = 269}
Interfaces
1type Shape interface {2 area() float643}4
5type Circle struct {6 x, y, radius float647}8
9type Rectangle struct {10 width, height float6411}12
13func (c Circle) area() float64 {14 return math.Pi * c.radius * c.radius15}16
17func (r Rectangle) area() float64 {18 return r.width * r.height19}20
21func getArea(s Shape) float64 {22 return s.area()23}
Web
To work with HTTP requests we can use thenet/http
package which allows us to define a function to handle a specific route
1package main2
3import ("fmt"4 "net/http"5)6
7func main(){8 http.HandleFunc("/", index)9 fmt.Println("Server Starting")10 http.ListenAndServe(":3000", nil)11}12
13func index(w http.ResponseWriter, r *http.Request) {14 fmt.Fprintf(w, "Hello World")15}
Randomness
The math/rand
package offers some utilities for generaating random numbers
Numbers generated by math/rand
are not truly random and can be seeded before retreiving random numbers, like so:
1rand.Seed(time.Now().UnixNano())2
3// random int between 0 and 1004num := rand.IntN(100)
JSON
Go has a builtin JSON library, it’s pretty straightforward to use. It consists of defining the JSON properties of the struct you’d like to parse and then parsing it using a decoder
For example:
1import (2 "encoding/json"3 "fmt"4 "strings"5)6
7type MyData struct {8 Name string `json:"name"`9 Age int `json:"age"`10}11
12func main() {13 data := strings.NewReader("{\"name\": \"Bob\", \"age\": 25 }")14
15 // decoder needs an io.Reader16 decoder := json.NewDecoder(data)17
18 var output MyData19 err := decoder.Decode(&output)20
21 if err != nil {22 panic(err)23 }24
25 fmt.Printf("Decoded JSON: %+v", output)26 // Decoded JSON: {Name:Bob Age:25}27}