🐹 Go Programming
Go (Golang) Complete Cheatsheet
Structs, interfaces, goroutines, channels, modules — complete Go language reference.
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.