Go logoGov1.26INTERMEDIATE

Go

Go (Golang) cheat sheet covering syntax, goroutines, channels, interfaces, error handling, and concurrency patterns with examples.

8 min read
gogolangbackendconcurrentsystemsapimicroservices

Sign in to mark items as known and track your progress.

Sign in

Setup & Basics

Getting started with Go

Installation & Setup

Installing Go and setting up development environment

bash
# Download and install Go
# Visit https://go.dev/dl/ for latest version

# Verify installation
go version

# Initialize a new module
go mod init example.com/myapp

# Run a Go program
go run main.go
🚀 Compiled language with fast execution
📦 Built-in dependency management with go mod
🔧 Simple toolchain with go command
⚡ Static binaries for easy deployment

Basic Syntax

Go program structure and syntax

go
package main

import "fmt"

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

// Variables
var name string = "Go"
age := 25  // Short declaration
📝 Static typing with type inference
🎯 Simple, clean syntax without semicolons
🔧 := for short variable declarations
📦 Package-based organization

Data Types & Structures

Go data types, arrays, slices, maps, and structs

Arrays & Slices

Working with arrays and slices

go
// Arrays (fixed size)
var arr [5]int
arr[0] = 10

// Slices (dynamic)
slice := []int{1, 2, 3}
slice = append(slice, 4)

// Make slice
s := make([]int, 5, 10) // len=5, cap=10
📊 Arrays have fixed size, slices are dynamic
🔄 append() to add elements to slices
📦 make() to create slices with capacity
⚡ Slices are references to underlying arrays

Maps

Working with maps (hash tables)

go
// Create map
m := make(map[string]int)
m["key"] = 42

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

// Check if key exists
val, ok := ages["Alice"]
🗺️ Hash tables with O(1) average access
🔑 Any comparable type can be a key
❌ Use delete() to remove entries
⚠️ Maps are not thread-safe by default

Structs

Defining and using structs

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

// Create instance
p := Person{Name: "Alice", Age: 25}
p.Age = 26  // Access field
📦 Group related data with structs
🏷️ Tags for JSON/DB field mapping
🔄 Methods with value or pointer receivers
🎯 Composition over inheritance with embedding

Struct Embedding

Composition via embedding structs and interfaces

go
// Embed struct for composition
type Address struct {
    City    string
    Country string
}

type Person struct {
    Name string
    Address  // Embedded (promoted fields)
}

p := Person{
    Name:    "Alice",
    Address: Address{City: "NYC", Country: "US"},
}
fmt.Println(p.City) // "NYC" (promoted)
🎯 Go uses composition over inheritance via embedding
💡 Embedded fields and methods are promoted to outer struct
📌 Access via p.City or p.Address.City — both work
⚡ Interfaces can embed other interfaces for composition

Pointers

Working with pointers and memory addresses

Pointers

Pointer basics, dereferencing, and nil pointers

go
// Declare a pointer
var p *int

// Get address of variable
x := 42
p = &x

// Dereference (access value)
fmt.Println(*p) // 42

// Modify via pointer
*p = 100
fmt.Println(x) // 100

// Nil pointer check
if p != nil {
    fmt.Println(*p)
}
🎯 Use & to get address, * to dereference
📌 Go has no pointer arithmetic (safer than C)
💡 Struct fields accessed directly through pointer (no -> needed)
⚡ new() allocates zeroed memory and returns a pointer

Control Flow

Conditionals, loops, and control structures

Conditionals

If statements and switch cases

go
// If statement
if x > 0 {
    fmt.Println("Positive")
} else if x < 0 {
    fmt.Println("Negative")
} else {
    fmt.Println("Zero")
}

// Switch
switch day {
case 1:
    fmt.Println("Monday")
case 2:
    fmt.Println("Tuesday")
default:
    fmt.Println("Other")
}
🎯 No parentheses needed around conditions
🔄 Switch doesn't need break (no fallthrough by default)
📦 Type switches for interface type assertion
⚡ Switch can be used without an expression

Loops

For loops and iterations

go
// Basic for loop
for i := 0; i < 10; i++ {
    fmt.Println(i)
}

// While-style loop
for x < 100 {
    x *= 2
}

// Range over slice
for i, v := range slice {
    fmt.Printf("%d: %v\n", i, v)
}
🔄 Only for loops (no while/do-while)
📊 range for iterating collections
🏷️ Labels for breaking nested loops
⚡ range on channels until closed

Functions

Functions, methods, and closures

Functions

Function declaration and usage

go
// Basic function
func add(x, y int) int {
    return x + y
}

// Multiple returns
func swap(x, y string) (string, string) {
    return y, x
}

// Named returns
func split(sum int) (x, y int) {
    x = sum * 4 / 9
    y = sum - x
    return  // Naked return
}
📦 Multiple return values for errors
🔄 defer for cleanup (LIFO order)
📝 Variadic functions with ...
⚡ Functions are first-class values

Methods

Methods on types and interfaces

go
type Rectangle struct {
    Width, Height float64
}

// Value receiver
func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

// Pointer receiver
func (r *Rectangle) Scale(factor float64) {
    r.Width *= factor
    r.Height *= factor
}
📦 Methods attached to types with receivers
🔄 Value receivers can't modify, pointer receivers can
🎯 Go auto-dereferences and takes addresses
⚡ Methods can be on any type, not just structs

Closures

Anonymous functions and closures

go
// Anonymous function
greet := func(name string) string {
    return "Hello, " + name
}
fmt.Println(greet("Go"))

// Closure captures variables
counter := func() func() int {
    n := 0
    return func() int {
        n++
        return n
    }
}()
fmt.Println(counter()) // 1
fmt.Println(counter()) // 2
🔄 Closures capture variables by reference, not by value
💡 Common for goroutines, callbacks, and factory functions
📌 Go 1.22+ fixed the loop variable capture bug
⚡ Functions are first-class values in Go

Interfaces

Interface types and polymorphism

Interfaces

Defining and implementing interfaces

go
// Define interface
type Shape interface {
    Area() float64
    Perimeter() float64
}

// Implement interface
type Rectangle struct {
    Width, Height float64
}

func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}
🎯 Implicit interface implementation
📦 interface{} for any type
🔄 Type assertions and type switches
⚡ Small interfaces are better (Interface Segregation)

Type Assertions & Switches

Extract concrete types from interfaces

go
// Type assertion
var i interface{} = "hello"
s := i.(string)        // Panics if wrong type
s, ok := i.(string)    // Safe: ok = false if wrong

// Type switch
switch v := i.(type) {
case string:
    fmt.Println("string:", v)
case int:
    fmt.Println("int:", v)
default:
    fmt.Println("unknown")
}
📌 Always use the comma-ok form to avoid panics
💡 Type switches use .(type) — only works in switch statements
🎯 any is an alias for interface{} since Go 1.18
⚡ Type assertions work on interfaces, not concrete types

Generics

Type parameters and constraints

Generics

Generic functions and types with type parameters

go
// Generic function
func Map[T any, U any](s []T, f func(T) U) []U {
    result := make([]U, len(s))
    for i, v := range s {
        result[i] = f(v)
    }
    return result
}

// Constrained generic
func Min[T int | float64 | string](a, b T) T {
    if a < b { return a }
    return b
}
🎯 Available since Go 1.18 — use any for unconstrained types
💡 ~ means underlying type (e.g., ~int matches type MyInt int)
📌 comparable constraint allows == and != operations
⚡ Type inference often lets you omit type arguments at call site

Concurrency

Goroutines, channels, and synchronization

Goroutines

Concurrent execution with goroutines

go
// Start goroutine
go doSomething()

// Anonymous goroutine
go func() {
    fmt.Println("Hello from goroutine")
}()

// Wait for completion
time.Sleep(time.Second)
🚀 Lightweight threads managed by Go runtime
⚡ Can run millions of goroutines
🔄 Use sync.WaitGroup for synchronization
⚠️ Beware of race conditions - use channels or mutexes

Channels

Communication between goroutines

go
// Create channel
ch := make(chan int)

// Send to channel
ch <- 42

// Receive from channel
value := <-ch

// Buffered channel
ch := make(chan int, 100)
📡 Channels for goroutine communication
🔄 Buffered vs unbuffered channels
🎯 select for multiplexing channel operations
⏱️ Timeouts with time.After()

Select Statement

Multiplex channel operations with select

go
// Select waits on multiple channels
select {
case msg := <-ch1:
    fmt.Println("From ch1:", msg)
case msg := <-ch2:
    fmt.Println("From ch2:", msg)
case ch3 <- "hello":
    fmt.Println("Sent to ch3")
default:
    fmt.Println("No channel ready")
}
🎯 select blocks until one case is ready (random if multiple)
⏱️ Use time.After for timeout patterns
💡 default case makes select non-blocking
📌 Common for fan-in, timeouts, and cancellation

Sync Primitives

Mutex, WaitGroup, and Once for synchronization

go
// WaitGroup: wait for goroutines
var wg sync.WaitGroup
for i := range 5 {
    wg.Add(1)
    go func() {
        defer wg.Done()
        fmt.Println(i)
    }()
}
wg.Wait()

// Mutex: protect shared state
var mu sync.Mutex
mu.Lock()
// critical section
mu.Unlock()
📌 Always defer wg.Done() and mu.Unlock() to prevent deadlocks
💡 Use RWMutex when reads vastly outnumber writes
🎯 sync.Once is perfect for lazy initialization
⚡ Prefer channels for communication, mutexes for state protection

Error Handling

Error handling patterns and best practices

Error Handling

Working with errors in Go

go
// Return error
func divide(x, y float64) (float64, error) {
    if y == 0 {
        return 0, errors.New("division by zero")
    }
    return x / y, nil
}

// Check error
result, err := divide(10, 0)
if err != nil {
    log.Fatal(err)
}
❌ Errors are values, not exceptions
🔄 Always check returned errors
📦 Wrap errors with context using %w
🎯 errors.Is() and errors.As() for error checking

Defer, Panic & Recover

Deferred execution, panics, and recovery

go
// Defer: runs when function returns (LIFO)
f, _ := os.Open("file.txt")
defer f.Close() // Guaranteed cleanup

// Panic: unrecoverable error
panic("something went wrong")

// Recover: catch panics
defer func() {
    if r := recover(); r != nil {
        fmt.Println("Recovered:", r)
    }
}()
📌 defer runs in LIFO order when the function returns
🎯 Always defer Close(), Unlock(), Done() right after acquiring
⚠️ Only recover from panics at package boundaries — don't use as try/catch
💡 Deferred functions can modify named return values

String Formatting

fmt package verbs and string operations

fmt Verbs & Formatting

Printf verbs and string formatting functions

go
// Common verbs
fmt.Printf("%v", val)    // Default format
fmt.Printf("%+v", s)     // Struct with field names
fmt.Printf("%#v", s)     // Go-syntax representation
fmt.Printf("%T", val)    // Type of value
fmt.Printf("%d", 42)     // Integer
fmt.Printf("%s", "hi")   // String
fmt.Printf("%f", 3.14)   // Float
fmt.Printf("%t", true)   // Boolean
fmt.Printf("%p", &val)   // Pointer

// Sprintf returns string
msg := fmt.Sprintf("Hello, %s!", name)
🎯 %v is the go-to verb — works with any type
💡 %+v shows struct field names — great for debugging
📌 Sprintf returns a string, Printf writes to stdout
⚡ Implement String() method to customize %v output

Context

Cancellation, timeouts, and request-scoped values

Context

Cancellation, timeouts, and passing request-scoped data

go
// With cancel
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

// With timeout
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()

// Check if done
select {
case <-ctx.Done():
    fmt.Println(ctx.Err()) // context canceled
}
📌 Always pass context as the first function parameter
🎯 Always defer cancel() to prevent context leaks
💡 Use WithValue sparingly — prefer explicit parameters
⚡ context.TODO() for placeholder when unsure which context to use

JSON

Encoding and decoding JSON data

JSON Encoding & Decoding

Marshal, Unmarshal, and struct tags for JSON

go
// Struct with JSON tags
type User struct {
    Name  string `json:"name"`
    Email string `json:"email,omitempty"`
    Age   int    `json:"age"`
}

// Encode (struct → JSON)
data, _ := json.Marshal(user)

// Decode (JSON → struct)
var u User
json.Unmarshal(data, &u)
📌 Use struct tags to control JSON field names and behavior
💡 omitempty skips zero-value fields, "-" excludes entirely
⚡ Use map[string]any for dynamic/unknown JSON structures
🎯 NewEncoder/NewDecoder for streaming — more efficient than Marshal/Unmarshal

HTTP Server & Client

Building HTTP servers and making requests

HTTP Server & Client

net/http server handlers and HTTP client requests

go
// Simple server
http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, World!")
})
http.ListenAndServe(":8080", nil)

// GET request
resp, _ := http.Get("https://api.example.com/data")
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
🎯 Go 1.22+ ServeMux supports method + path patterns natively
📌 Always defer resp.Body.Close() on HTTP responses
💡 Set server timeouts to prevent slow-client attacks
⚡ Use http.Client with timeout — default has no timeout

Testing

Writing and running tests

Testing

Unit tests and benchmarks

go
// math_test.go
func TestAdd(t *testing.T) {
    result := Add(2, 3)
    if result != 5 {
        t.Errorf("Add(2, 3) = %d; want 5", result)
    }
}

// Run tests
// go test
// go test -v
// go test -cover
🧪 Built-in testing with testing package
📊 Table-driven tests for multiple cases
⚡ Benchmarks with go test -bench
📈 Coverage with go test -cover

Packages & Modules

Package management and module system

Packages & Imports

Organizing code with packages

go
// Package declaration
package main

// Imports
import (
    "fmt"
    "math/rand"
    
    "github.com/user/package"
)

// Package alias
import m "math"
📦 Packages for code organization
🔒 Capitalized names are exported
📁 internal/ for private packages
⚡ go.mod for dependency management