🐹 Go Programming
Go (Golang) Complete Cheatsheet
Structs, interfaces, goroutines, channels, modules — complete Go language reference.
📖 10 sections
⏱ 24 min read
✅ Quizzes included
🌙 Dark mode
01 Variables & Types
GOGo basics
package main

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

func main() {
    // Variable declarations
    var name string = "Ali"          // explicit type
    age := 22                         // inferred type (:=)
    const MAX = 100                   // constant
    var active bool                   // zero value: false

    // Multiple assignment
    x, y := 10, 20
    x, y = y, x  // swap!

    // Blank identifier
    result, _ := strconv.Atoi("42")  // ignore second return

    // Strings
    fmt.Println("Hello, " + name)
    fmt.Printf("Age: %d, Pi: %.2f\n", age, 3.14)
    fmt.Sprintf("Hello %s", name)     // returns string
    strings.ToUpper(name)
    strings.Contains(name, "li")
    strings.Split("a,b,c", ",")
    strings.TrimSpace("  hi  ")

    // Type conversion (explicit only)
    f := float64(age)
    s := strconv.Itoa(age)            // int to string
}
💡
Go has zero values: 0 for ints, '' for strings, false for bools, nil for pointers/maps/slices/channels/interfaces.
02 Functions
GOFunctions
// Basic function
func add(a, b int) int {
    return a + b
}

// Multiple return values (very common in Go!)
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("division by zero")
    }
    return a / b, nil
}

// Named return values
func stats(nums []int) (min, max, sum int) {
    min, max = nums[0], nums[0]
    for _, n := range nums {
        if n < min { min = n }
        if n > max { max = n }
        sum += n
    }
    return  // naked return
}

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

// First-class function
operate := func(a, b int) int { return a + b }

// Defer — runs at function exit (LIFO)
defer fmt.Println("cleanup")  // runs last
defer file.Close()             // common pattern
💡
Multiple return values are idiomatic Go. The convention: last return value is always error (or nil if success).
03 Structs
GOStructs — Go's classes
// Define struct
type Person struct {
    Name    string
    Age     int
    Email   string
}

// Methods (receiver functions)
func (p Person) Greet() string {
    return fmt.Sprintf("Hi, I'm %s, age %d", p.Name, p.Age)
}

// Pointer receiver (to modify)
func (p *Person) Birthday() {
    p.Age++
}

// Constructor (by convention)
func NewPerson(name string, age int) *Person {
    return &Person{Name: name, Age: age}
}

// Embedding (Go's inheritance)
type Employee struct {
    Person             // embedded — inherits fields + methods
    Company   string
    Salary    float64
}

// Usage
ali := NewPerson("Ali", 22)
ali.Birthday()           // mutates via pointer receiver
fmt.Println(ali.Greet())

emp := Employee{
    Person:  Person{Name: "Sara", Age: 25},
    Company: "BitWithBite",
    Salary:  80000,
}
fmt.Println(emp.Greet())  // promoted method from Person
Value receiver
func (p Person) Greet() — works on copy, doesn't modify
Pointer receiver
func (p *Person) Birthday() — modifies original, use for mutations
Struct tags
struct { ID int `json:"id" db:"user_id"` } — for JSON/DB mapping
04 Interfaces
GOInterfaces
// Define interface
type Animal interface {
    Sound() string
    Name() string
}

// Implement (implicit! no 'implements' keyword)
type Dog struct{ name string }
func (d Dog) Sound() string { return "Woof!" }
func (d Dog) Name() string  { return d.name }

type Cat struct{ name string }
func (c Cat) Sound() string { return "Meow!" }
func (c Cat) Name() string  { return c.name }

// Polymorphism
func makeSound(a Animal) {
    fmt.Printf("%s says: %s\n", a.Name(), a.Sound())
}

animals := []Animal{Dog{"Rex"}, Cat{"Whiskers"}}
for _, a := range animals { makeSound(a) }

// Empty interface — holds any type
func printAny(v interface{}) {
    fmt.Printf("%T: %v\n", v, v)  // print type and value
}

// Type assertion
var i interface{} = "hello"
s, ok := i.(string)       // s = "hello", ok = true

// Type switch
switch v := i.(type) {
case int:    fmt.Println("int:", v)
case string: fmt.Println("string:", v)
default:     fmt.Println("other type")
}
💡
Go interfaces are satisfied implicitly — if a type has all the methods, it implements the interface. No declaration needed.
05 Error Handling
GOError handling
// Errors are values in Go
import "errors"

// Creating errors
err := errors.New("something went wrong")
err = fmt.Errorf("failed to open %s: %w", filename, err)  // wrap error

// Custom error type
type ValidationError struct {
    Field   string
    Message string
}
func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation failed: %s — %s", e.Field, e.Message)
}

// Error handling pattern
result, err := riskyOperation()
if err != nil {
    return fmt.Errorf("riskyOperation failed: %w", err)  // wrap + return
}
// use result...

// Unwrap errors
var valErr *ValidationError
if errors.As(err, &valErr) {
    fmt.Println("Field:", valErr.Field)
}
if errors.Is(err, os.ErrNotExist) {
    fmt.Println("file not found")
}

// panic/recover — for truly unexpected errors
func safeDiv(a, b int) (result int, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("recovered panic: %v", r)
        }
    }()
    return a / b, nil
}
⚠️
Don't use panic for regular error handling. Return error values. Reserve panic for truly unrecoverable states.
06 Goroutines & Channels
GOGoroutines & Channels — Go's superpower
// Goroutine — lightweight thread
go func() {
    fmt.Println("running concurrently")
}()

// Channel — communicate between goroutines
ch := make(chan int)        // unbuffered
ch := make(chan int, 10)    // buffered (non-blocking until full)

// Send and receive
go func() { ch <- 42 }()   // send
value := <-ch               // receive (blocks until data available)

// WaitGroup — wait for multiple goroutines
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
    wg.Add(1)
    go func(n int) {
        defer wg.Done()
        fmt.Printf("Worker %d done\n", n)
    }(i)
}
wg.Wait()  // block until all done

// Select — multiplex channels
select {
case msg1 := <-ch1:
    fmt.Println("from ch1:", msg1)
case msg2 := <-ch2:
    fmt.Println("from ch2:", msg2)
case <-time.After(1 * time.Second):
    fmt.Println("timeout!")
}

// Mutex — protect shared data
var mu sync.Mutex
mu.Lock()
sharedCounter++
mu.Unlock()
💡
'Do not communicate by sharing memory; share memory by communicating.' — use channels to pass data between goroutines.
07 Packages & Modules
GOPackages & Modules
# Initialize module
go mod init github.com/username/myproject

# Add dependency
go get github.com/gin-gonic/gin

# Update all dependencies
go get -u ./...

# Tidy — remove unused, add missing
go mod tidy

# Run, build, test
go run main.go
go build -o myapp ./...
go test ./...
go test -v -run TestName
go test -cover ./...

# Format and vet
gofmt -w .
go vet ./...

# go.mod example
module github.com/user/project

go 1.21

require (
    github.com/gin-gonic/gin v1.9.1
    gorm.io/gorm v1.25.0
)

// Package naming — file: utils/math.go
package utils

// Exported = Capital first letter
func Add(a, b int) int { return a + b }   // exported
func helper() {}                           // unexported
Exported
Capital letter = public. Lowercase = package-private.
init()
Runs automatically before main(). Each file can have one.
go.sum
Cryptographic hashes of dependencies. Commit to version control.
08 Slices & Maps
GOSlices & Maps
// Slices (dynamic arrays)
nums := []int{1, 2, 3, 4, 5}
nums = append(nums, 6, 7)          // append
nums2 := append(nums, others...)   // spread
slice := nums[1:4]                  // [2,3,4] (shares memory!)
copy(dest, src)                     // copy (doesn't share)

// 2D slice
matrix := [][]int{{1,2},{3,4},{5,6}}

// Make (allocate)
s := make([]int, 5)        // length 5, zero values
s := make([]int, 0, 100)   // length 0, capacity 100
fmt.Println(len(s), cap(s))

// Range over slice
for i, v := range nums {
    fmt.Println(i, v)
}
for _, v := range nums { /* just values */ }

// Maps
m := map[string]int{"a": 1, "b": 2}
m = make(map[string]int)
m["key"] = 42
val, ok := m["key"]     // ok = false if missing
delete(m, "key")

// Range over map
for k, v := range m {
    fmt.Println(k, v)
}
⚠️
Slices share underlying array. Modifying a slice can affect the original. Use copy() to avoid this.
09 Pointers
GOPointers
// Address and dereference
age := 22
ptr := &age          // ptr holds address of age
*ptr = 30            // dereference and modify age
fmt.Println(age)     // 30

// new() — allocate zero value
p := new(int)        // *int, *p == 0

// When to use pointers
// 1. Modify a value in a function
func increment(n *int) { *n++ }
increment(&age)

// 2. Avoid copying large structs
func (p *LargeStruct) Method() {}

// 3. Nil check (optional values)
func findUser(id int) *User {
    // ... search ...
    return nil   // user not found
}
if user := findUser(1); user != nil {
    fmt.Println(user.Name)
}

// No pointer arithmetic in Go
// No dangling pointers — GC handles memory
💡
Go has garbage collection — no manual memory management. Pointers are safe; no arithmetic allowed.
10 Mini Quizzes
❓ Quiz 1
What makes goroutines different from OS threads?
Goroutines are multiplexed onto OS threads by the Go scheduler. They start with ~2KB stack (vs ~1MB for threads) and can scale to millions. Channels are the idiomatic way to communicate between them.
❓ Quiz 2
In Go, how does a type implement an interface?
Go interfaces are satisfied implicitly. If a type has all the methods an interface specifies, it implements that interface — no declaration required. This enables loose coupling.