Back to Go Examples

Basic Go

A quick-reference instruction sheet covering the fundamentals of Go (Golang) programming.

Contents

  1. What is Go?
  2. Program Structure
  3. Variables & Data Types
  4. Operators
  5. Strings
  6. Arrays, Slices & Maps
  7. Control Structures
  8. Loops
  9. Functions
  10. Structs & Methods
  11. Interfaces
  12. Goroutines & Channels

1. What is Go?

Go (also called Golang) is a statically typed, compiled language created at Google. It is designed for simplicity, fast compilation, and built-in concurrency support.

2. Program Structure

package main

import "fmt"

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

Compile and run:

go run main.go
go build -o myapp main.go   // produces a binary
./myapp
Common packages: fmt (I/O), math, strings, strconv, os, errors, net/http.

3. Variables & Data Types

// var declaration — explicit type
var name   string  = "Alice"
var age    int     = 30
var price  float64 = 9.99
var active bool    = true

// Short variable declaration — type inferred (inside functions only)
city  := "NYC"
count := 10

// Multiple assignment
x, y := 1, 2

// Zero values — Go initialises all variables
var i int     // 0
var s string  // ""
var b bool    // false

// Constants
const Pi  = 3.14159
const Max = 100
TypeDescriptionZero value
int, int8/16/32/64Signed integers0
uint, uint8/16/32/64Unsigned integers0
float32, float64Floating-point numbers0
stringImmutable UTF-8 text""
boolBooleanfalse
byteAlias for uint80
runeAlias for int32 — Unicode code point0

4. Operators

a, b := 10, 3
a + b    // 13
a - b    // 7
a * b    // 30
a / b    // 3  — integer division
a % b    // 1  — modulus

// Comparison
a == b   // false
a != b   // true
a <  b   // false
a >  b   // true

// Logical
a > 5 && b < 5   // true  — AND
a > 5 || b > 5   // true  — OR
!(a == b)         // true  — NOT

// Assignment shortcuts
a += 5
a++       // increment (statement only, not expression)
In Go, ++ and -- are statements, not expressions — you cannot write x = a++.

5. Strings

import (
    "fmt"
    "strings"
    "strconv"
)

first := "Alice"
last  := "Smith"

// Concatenation
first + " " + last                    // "Alice Smith"

// Sprintf — formatted string (like printf but returns a string)
fmt.Sprintf("Hello, %s! Age: %d", first, 30)

// Raw string literal — backticks, no escape processing
path := `C:\Users\Alice`

// strings package
strings.ToUpper("hello")             // "HELLO"
strings.ToLower("HELLO")             // "hello"
strings.TrimSpace("  hi  ")         // "hi"
strings.Contains("hello", "ell")    // true
strings.HasPrefix("hello", "he")    // true
strings.Replace("aaa", "a", "b", 2) // "bba"
strings.Split("a,b,c", ",")         // ["a","b","c"]
strings.Join([]string{"a","b"}, "-") // "a-b"

// Convert between strings and numbers
strconv.Itoa(42)                      // "42"
strconv.Atoi("42")                    // 42, nil

6. Arrays, Slices & Maps

Arrays — fixed size

var a [3]int = [3]int{10, 20, 30}
a[0]          // 10
len(a)        // 3

Slices — dynamic, use these in practice

fruits := []string{"apple", "banana", "cherry"}

fruits[0]                        // "apple"
fruits[1:3]                      // ["banana","cherry"] — slice of slice
len(fruits)                      // 3
cap(fruits)                      // underlying array capacity

fruits = append(fruits, "date") // append — may allocate new array

// Make a slice with length and capacity
s := make([]int, 5)              // [0,0,0,0,0]
s := make([]int, 3, 10)         // length 3, capacity 10

// Copy
dst := make([]int, len(src))
copy(dst, src)

Maps

ages := map[string]int{
    "Alice": 30,
    "Bob":   25,
}

ages["Alice"]                    // 30
ages["Carol"] = 28               // add / update
delete(ages, "Bob")              // remove key

// Safe lookup — ok is false if key missing
val, ok := ages["Dave"]
if !ok {
    fmt.Println("not found")
}

// Make an empty map
m := make(map[string]int)

7. Control Structures

if / else if / else

score := 75

if score >= 90 {
    fmt.Println("A")
} else if score >= 75 {
    fmt.Println("B")
} else {
    fmt.Println("C or below")
}

// if with initialiser — x is scoped to the if block
if x := compute(); x > 10 {
    fmt.Println(x)
}

switch

// No fallthrough by default — no break needed
switch day {
case "Mon":
    fmt.Println("Monday")
case "Fri", "Sat":
    fmt.Println("End of week")
default:
    fmt.Println("Other")
}

// switch with no condition — acts like if/else chain
switch {
case score >= 90:
    fmt.Println("A")
case score >= 75:
    fmt.Println("B")
}

8. Loops

Go has only one loop keyword: for. It covers all looping patterns.

Traditional for

for i := 0; i < 5; i++ {
    fmt.Println(i)
}

while-style

i := 0
for i < 5 {
    fmt.Println(i)
    i++
}

range — iterate over slices, maps, strings

fruits := []string{"apple", "banana", "cherry"}

for i, fruit := range fruits {
    fmt.Printf("%d: %s\n", i, fruit)
}

// Ignore index with _
for _, fruit := range fruits {
    fmt.Println(fruit)
}

// Range over a map
for key, val := range ages {
    fmt.Printf("%s: %d\n", key, val)
}
Use break to exit a loop and continue to skip to the next iteration.

9. Functions

// Basic function
func greet(name string) string {
    return "Hello, " + name + "!"
}

// Multiple return values — idiomatic Go
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("cannot divide by zero")
    }
    return a / b, nil
}

result, err := divide(10, 3)
if err != nil {
    fmt.Println("Error:", err)
}

// Variadic function
func sum(nums ...int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}
sum(1, 2, 3, 4)    // 10

// Function as a value / closure
double := func(n int) int { return n * 2 }
double(5)           // 10

// defer — run at end of the surrounding function
defer fmt.Println("cleanup")   // runs when function returns
Returning an error as the last return value is the standard Go pattern for error handling — there are no exceptions.

10. Structs & Methods

// Define a struct
type Person struct {
    Name string
    Age  int
}

// Create instances
p1 := Person{Name: "Alice", Age: 30}
p2 := Person{"Bob", 25}              // positional (order matters)

p1.Name                                // "Alice"
p1.Age = 31                           // update field

// Pointer to struct
ptr := &p1
ptr.Age = 32                          // Go auto-dereferences

// Method — function with a receiver
func (p Person) Describe() string {
    return fmt.Sprintf("%s is %d years old.", p.Name, p.Age)
}

// Pointer receiver — can modify the struct
func (p *Person) Birthday() {
    p.Age++
}

fmt.Println(p1.Describe())           // Alice is 32 years old.
p1.Birthday()                         // p1.Age is now 33

11. Interfaces

An interface defines a set of method signatures. Any type that implements all the methods satisfies the interface — no explicit declaration needed.

type Shape interface {
    Area()      float64
    Perimeter() float64
}

type Circle struct { Radius float64 }
type Rect   struct { W, H float64 }

func (c Circle) Area()      float64 { return math.Pi * c.Radius * c.Radius }
func (c Circle) Perimeter() float64 { return 2 * math.Pi * c.Radius }
func (r Rect)   Area()      float64 { return r.W * r.H }
func (r Rect)   Perimeter() float64 { return 2*(r.W+r.H) }

// Both Circle and Rect satisfy Shape
func printArea(s Shape) {
    fmt.Printf("Area: %.2f\n", s.Area())
}

printArea(Circle{Radius: 5})
printArea(Rect{W: 4, H: 6})
The empty interface interface{} (or any in Go 1.18+) accepts any type.

12. Goroutines & Channels

Go has built-in concurrency via goroutines (lightweight threads) and channels (typed message-passing pipes).

Goroutines

import (
    "fmt"
    "time"
)

func say(msg string) {
    for i := 0; i < 3; i++ {
        fmt.Println(msg)
        time.Sleep(100 * time.Millisecond)
    }
}

func main() {
    go say("world")   // launches a goroutine
    say("hello")      // runs in the main goroutine
}

Channels

func main() {
    // Create a channel of ints
    ch := make(chan int)

    // Send a value in a goroutine
    go func() {
        ch <- 42   // send
    }()

    val := <-ch   // receive (blocks until value arrives)
    fmt.Println(val)   // 42
}

// Buffered channel — send doesn't block until buffer is full
ch := make(chan int, 5)

// WaitGroup — wait for multiple goroutines to finish
import "sync"

var wg sync.WaitGroup
for i := 0; i < 3; i++ {
    wg.Add(1)
    go func(n int) {
        defer wg.Done()
        fmt.Println(n)
    }(i)
}
wg.Wait()   // blocks until all goroutines call Done()
Go's concurrency motto: "Do not communicate by sharing memory; instead, share memory by communicating." Prefer channels over shared variables.