Skip to content
Published on

Go Language Complete Syntax Guide: From Basics to Concurrency and Generics

Authors

Introduction

Go (Golang) is a statically typed, compiled language released by Google in 2009. It is known for its simple syntax, fast compilation, powerful concurrency support, and rich standard library. Go excels at cloud infrastructure, microservices, and CLI tool development.

This guide systematically covers Go's core syntax from start to finish.


1. Go Language Basics

1.1 Variable Declarations

Go offers several ways to declare variables.

package main

import "fmt"

func main() {
    // var keyword with explicit type
    var name string = "Go"
    var age int = 15

    // Type inference with var
    var pi = 3.14159

    // Short declaration (:=) — only inside functions
    greeting := "Hello, Go!"
    count := 42

    // Multiple variable declaration
    var x, y int = 10, 20
    a, b := 100, 200

    // Zero values: uninitialized variables get their type's default value
    var zeroInt int       // 0
    var zeroStr string    // ""
    var zeroBool bool     // false

    fmt.Println(name, age, pi, greeting, count)
    fmt.Println(x, y, a, b)
    fmt.Println(zeroInt, zeroStr, zeroBool)
}

1.2 Constants and iota

package main

import "fmt"

// Simple constants
const Pi = 3.14159
const MaxSize = 1024

// Constant block
const (
    StatusOK       = 200
    StatusNotFound = 404
    StatusError    = 500
)

// iota: auto-incrementing enumeration constants
type Direction int

const (
    North Direction = iota // 0
    East                   // 1
    South                  // 2
    West                   // 3
)

type ByteSize float64

const (
    _           = iota // skip 0 with blank identifier
    KB ByteSize = 1 << (10 * iota) // 1 << 10 = 1024
    MB                              // 1 << 20
    GB                              // 1 << 30
)

func main() {
    fmt.Println(North, East, South, West) // 0 1 2 3
    fmt.Printf("KB=%.0f, MB=%.0f, GB=%.0f\n", float64(KB), float64(MB), float64(GB))
}

1.3 Basic Types

package main

import "fmt"

func main() {
    // Integer types
    var i8 int8 = 127
    var i16 int16 = 32767
    var i32 int32 = 2147483647
    var i64 int64 = 9223372036854775807
    var ui uint = 42

    // Platform-dependent (int64 on 64-bit systems)
    var n int = 100

    // Floating point
    var f32 float32 = 3.14
    var f64 float64 = 3.141592653589793

    // Complex numbers
    var c128 complex128 = 3 + 4i

    // string (UTF-8 encoded, immutable)
    s := "Hello, World"
    fmt.Println(len(s))        // byte count
    fmt.Println([]rune(s))     // rune (Unicode code point) slice

    // byte (alias for uint8)
    var b byte = 'A'

    // rune (alias for int32, Unicode code point)
    var r rune = 'Z'

    // bool
    var flag bool = true

    fmt.Println(i8, i16, i32, i64, ui, n)
    fmt.Println(f32, f64, c128)
    fmt.Println(b, r, flag)
}

1.4 Arrays and Slices

package main

import "fmt"

func main() {
    // Array: fixed size, value type
    var arr [5]int
    arr[0] = 10
    arr2 := [3]string{"Go", "Python", "Rust"}
    arr3 := [...]int{1, 2, 3, 4, 5} // compiler infers size

    fmt.Println(arr, arr2, arr3)

    // Slice: dynamic size, reference type
    s1 := []int{1, 2, 3}
    s2 := make([]int, 3)       // len=3, cap=3
    s3 := make([]int, 3, 10)   // len=3, cap=10

    fmt.Println(len(s1), cap(s1)) // 3, 3
    fmt.Println(len(s2), cap(s2)) // 3, 3
    fmt.Println(len(s3), cap(s3)) // 3, 10

    // append: allocates new array when capacity exceeded
    s1 = append(s1, 4, 5)
    fmt.Println(s1) // [1 2 3 4 5]

    // Slicing (shares underlying memory with original)
    s4 := s1[1:3]  // [2 3]
    s5 := s1[2:]   // [3 4 5]
    s6 := s1[:2]   // [1 2]

    fmt.Println(s4, s5, s6)

    // copy: creates an independent copy
    dst := make([]int, len(s1))
    n := copy(dst, s1)
    fmt.Printf("Copied %d elements: %v\n", n, dst)

    // 2D slice
    matrix := make([][]int, 3)
    for i := range matrix {
        matrix[i] = make([]int, 3)
        for j := range matrix[i] {
            matrix[i][j] = i*3 + j
        }
    }
    fmt.Println(matrix)
}

1.5 Maps

package main

import "fmt"

func main() {
    // Map declaration and initialization
    m1 := map[string]int{
        "apple":  5,
        "banana": 3,
        "cherry": 8,
    }

    m2 := make(map[string][]string)

    // Add/update
    m1["date"] = 12
    m2["fruits"] = []string{"apple", "banana"}

    // Read value
    val := m1["apple"]
    fmt.Println(val) // 5

    // Comma-ok pattern: check key existence
    val2, ok := m1["mango"]
    if !ok {
        fmt.Println("mango not found, zero value:", val2)
    }

    // Delete
    delete(m1, "cherry")
    fmt.Println(m1)

    // Iteration (order not guaranteed)
    for key, value := range m1 {
        fmt.Printf("%s: %d\n", key, value)
    }

    // Writing to a nil map causes a panic
    var m3 map[string]int
    // m3["key"] = 1 // panic: assignment to entry in nil map
    _ = m3
}

1.6 Pointers

package main

import "fmt"

func increment(n *int) {
    *n++ // dereference
}

func newInt(val int) *int {
    return &val // returning address of local variable is fine (moved to heap)
}

func main() {
    x := 42
    p := &x        // address of x
    fmt.Println(p)  // memory address (e.g., 0xc0000b4010)
    fmt.Println(*p) // 42 (dereference)

    *p = 100
    fmt.Println(x) // 100

    increment(&x)
    fmt.Println(x) // 101

    ptr := newInt(200)
    fmt.Println(*ptr) // 200

    // nil pointer
    var nilPtr *int
    fmt.Println(nilPtr) // <nil>
    // fmt.Println(*nilPtr) // panic: nil pointer dereference
}

1.7 Control Structures

package main

import "fmt"

func main() {
    // for: Go's only loop construct
    for i := 0; i < 5; i++ {
        fmt.Print(i, " ")
    }
    fmt.Println()

    // while-style
    n := 1
    for n < 100 {
        n *= 2
    }
    fmt.Println(n)

    // range
    nums := []int{10, 20, 30, 40}
    for idx, val := range nums {
        fmt.Printf("[%d]=%d ", idx, val)
    }
    fmt.Println()

    // Index only
    for i := range nums {
        fmt.Print(nums[i], " ")
    }
    fmt.Println()

    // switch (no automatic fallthrough)
    day := "Monday"
    switch day {
    case "Saturday", "Sunday":
        fmt.Println("Weekend")
    case "Monday":
        fmt.Println("Monday")
    default:
        fmt.Println("Weekday")
    }

    // Switch without condition (replaces if-else chains)
    score := 85
    switch {
    case score >= 90:
        fmt.Println("A")
    case score >= 80:
        fmt.Println("B")
    case score >= 70:
        fmt.Println("C")
    default:
        fmt.Println("F")
    }

    // defer: executed when function exits (LIFO order)
    fmt.Println("start")
    defer fmt.Println("first defer")
    defer fmt.Println("second defer")
    fmt.Println("end")
    // Output: start -> end -> second defer -> first defer
}

2. Functions and Methods

2.1 Multiple Return Values and Named Returns

package main

import (
    "errors"
    "fmt"
)

// Multiple return values
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}

// Named return values
func minMax(nums []int) (min, max int) {
    if len(nums) == 0 {
        return // returns zero values
    }
    min, max = nums[0], nums[0]
    for _, n := range nums[1:] {
        if n < min {
            min = n
        }
        if n > max {
            max = n
        }
    }
    return // naked return
}

// Variadic functions
func sum(nums ...int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}

func main() {
    result, err := divide(10, 3)
    if err != nil {
        fmt.Println("Error:", err)
    } else {
        fmt.Printf("%.4f\n", result) // 3.3333
    }

    _, err = divide(5, 0)
    if err != nil {
        fmt.Println("Error:", err) // division by zero
    }

    min, max := minMax([]int{3, 1, 4, 1, 5, 9, 2, 6})
    fmt.Println(min, max) // 1 9

    fmt.Println(sum(1, 2, 3, 4, 5)) // 15

    // Spread slice as variadic argument
    nums := []int{10, 20, 30}
    fmt.Println(sum(nums...)) // 60
}

2.2 First-Class Functions and Closures

package main

import "fmt"

// Function as argument
func apply(nums []int, fn func(int) int) []int {
    result := make([]int, len(nums))
    for i, n := range nums {
        result[i] = fn(n)
    }
    return result
}

// Function as return value
func multiplier(factor int) func(int) int {
    return func(n int) int {
        return n * factor
    }
}

// Closure: captures variables from outer scope
func counter() func() int {
    count := 0
    return func() int {
        count++
        return count
    }
}

func main() {
    double := func(n int) int { return n * 2 }
    nums := []int{1, 2, 3, 4, 5}

    fmt.Println(apply(nums, double))    // [2 4 6 8 10]
    fmt.Println(apply(nums, func(n int) int { return n * n })) // [1 4 9 16 25]

    triple := multiplier(3)
    fmt.Println(triple(7)) // 21

    c1 := counter()
    c2 := counter()
    fmt.Println(c1(), c1(), c1()) // 1 2 3
    fmt.Println(c2(), c2())       // 1 2 (independent state)
}

2.3 Methods (Value Receiver vs Pointer Receiver)

package main

import (
    "fmt"
    "math"
)

type Circle struct {
    Radius float64
}

// Value receiver: operates on a copy (cannot modify original)
func (c Circle) Area() float64 {
    return math.Pi * c.Radius * c.Radius
}

func (c Circle) Perimeter() float64 {
    return 2 * math.Pi * c.Radius
}

// Pointer receiver: can modify the original
func (c *Circle) Scale(factor float64) {
    c.Radius *= factor
}

type Rectangle struct {
    Width, Height float64
}

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

func init() {
    fmt.Println("Package initialized")
}

func main() {
    c := Circle{Radius: 5}
    fmt.Printf("Area: %.2f\n", c.Area())
    fmt.Printf("Perimeter: %.2f\n", c.Perimeter())

    c.Scale(2)
    fmt.Printf("Radius after scale: %.1f\n", c.Radius) // 10.0
}

3. Structs and Interfaces

3.1 Structs and Embedding

package main

import "fmt"

type Animal struct {
    Name string
    Age  int
}

func (a Animal) Speak() string {
    return a.Name + " makes a sound"
}

// Embedding: composition over inheritance
type Dog struct {
    Animal       // embedded (anonymous field)
    Breed string
}

func (d Dog) Speak() string {
    return d.Name + " says: Woof!"
}

type ServiceDog struct {
    Dog
    ServiceType string
}

func main() {
    d := Dog{
        Animal: Animal{Name: "Buddy", Age: 3},
        Breed:  "Labrador",
    }

    // Accessing embedded fields directly
    fmt.Println(d.Name)         // same as d.Animal.Name
    fmt.Println(d.Age)
    fmt.Println(d.Breed)
    fmt.Println(d.Speak())         // Dog's Speak()
    fmt.Println(d.Animal.Speak())  // explicit Animal's Speak()

    sd := ServiceDog{
        Dog:         d,
        ServiceType: "Guide",
    }
    fmt.Println(sd.Name)        // sd.Dog.Animal.Name
    fmt.Println(sd.ServiceType)
}

3.2 Interfaces

package main

import (
    "fmt"
    "math"
)

// Interface definition
type Shape interface {
    Area() float64
    Perimeter() float64
}

type Stringer interface {
    String() string
}

type Circle struct{ Radius float64 }
type Rectangle struct{ Width, Height 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 (c Circle) String() string     { return fmt.Sprintf("Circle(r=%.1f)", c.Radius) }

func (r Rectangle) Area() float64      { return r.Width * r.Height }
func (r Rectangle) Perimeter() float64 { return 2 * (r.Width + r.Height) }
func (r Rectangle) String() string {
    return fmt.Sprintf("Rect(%.1fx%.1f)", r.Width, r.Height)
}

// Function accepting an interface
func printShapeInfo(s Shape) {
    fmt.Printf("Area: %.2f, Perimeter: %.2f\n", s.Area(), s.Perimeter())
}

// Empty interface (accepts any type)
func printAny(v interface{}) {
    fmt.Printf("Type: %T, Value: %v\n", v, v)
}

// Type assertion
func describe(i interface{}) {
    if s, ok := i.(string); ok {
        fmt.Printf("string: %q\n", s)
        return
    }
    if n, ok := i.(int); ok {
        fmt.Printf("int: %d\n", n)
        return
    }
    fmt.Printf("unknown type: %T\n", i)
}

// Type switch
func typeSwitch(v interface{}) string {
    switch x := v.(type) {
    case int:
        return fmt.Sprintf("int: %d", x)
    case string:
        return fmt.Sprintf("string: %q", x)
    case bool:
        return fmt.Sprintf("bool: %v", x)
    case []int:
        return fmt.Sprintf("[]int with %d elements", len(x))
    default:
        return fmt.Sprintf("unknown type: %T", x)
    }
}

func main() {
    shapes := []Shape{
        Circle{Radius: 5},
        Rectangle{Width: 4, Height: 6},
    }

    for _, s := range shapes {
        printShapeInfo(s)
        if str, ok := s.(Stringer); ok {
            fmt.Println(str.String())
        }
    }

    printAny(42)
    printAny("hello")
    printAny([]int{1, 2, 3})

    describe("Go language")
    describe(2024)

    fmt.Println(typeSwitch(100))
    fmt.Println(typeSwitch("world"))
    fmt.Println(typeSwitch([]int{1, 2}))
}

4. Error Handling

4.1 Error Types and Custom Errors

package main

import (
    "errors"
    "fmt"
)

// Sentinel errors (package-level error variables)
var (
    ErrNotFound   = errors.New("not found")
    ErrPermission = errors.New("permission denied")
)

// Custom error type
type ValidationError struct {
    Field   string
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation error: %s - %s", e.Field, e.Message)
}

type DatabaseError struct {
    Code    int
    Message string
    Err     error // wrapped error
}

func (e *DatabaseError) Error() string {
    return fmt.Sprintf("database error [%d]: %s", e.Code, e.Message)
}

func (e *DatabaseError) Unwrap() error {
    return e.Err
}

// Error wrapping with fmt.Errorf
func findUser(id int) error {
    if id <= 0 {
        return fmt.Errorf("findUser: invalid id %d: %w", id, ErrNotFound)
    }
    return nil
}

func getUser(id int) error {
    if err := findUser(id); err != nil {
        return fmt.Errorf("getUser: %w", err)
    }
    return nil
}

func main() {
    // errors.Is: check for a specific error in the chain
    err := getUser(-1)
    if errors.Is(err, ErrNotFound) {
        fmt.Println("not found error:", err)
    }

    // errors.As: extract a specific type from the chain
    dbErr := &DatabaseError{
        Code:    500,
        Message: "connection failed",
        Err:     ErrPermission,
    }
    wrapped := fmt.Errorf("service error: %w", dbErr)

    var de *DatabaseError
    if errors.As(wrapped, &de) {
        fmt.Printf("DB error code: %d\n", de.Code)
    }

    if errors.Is(wrapped, ErrPermission) {
        fmt.Println("permission error found in chain")
    }

    valErr := &ValidationError{Field: "email", Message: "invalid format"}
    fmt.Println(valErr)
}

4.2 panic and recover

package main

import "fmt"

// Recover from panic
func safeDiv(a, b int) (result int, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("recovered from panic: %v", r)
        }
    }()
    return a / b, nil
}

func mustPositive(n int) int {
    if n <= 0 {
        panic(fmt.Sprintf("value must be positive: %d", n))
    }
    return n
}

func main() {
    result, err := safeDiv(10, 2)
    fmt.Printf("10/2 = %d, err = %v\n", result, err)

    result, err = safeDiv(10, 0)
    fmt.Printf("10/0 = %d, err = %v\n", result, err)

    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Panic recovered:", r)
        }
    }()
    mustPositive(-5)
}

5. Goroutines and Channels (Concurrency)

5.1 Goroutines and WaitGroup

package main

import (
    "fmt"
    "sync"
    "time"
)

func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done()
    fmt.Printf("Worker %d starting\n", id)
    time.Sleep(time.Millisecond * 100)
    fmt.Printf("Worker %d done\n", id)
}

func main() {
    var wg sync.WaitGroup

    for i := 1; i <= 5; i++ {
        wg.Add(1)
        go worker(i, &wg)
    }

    wg.Wait()
    fmt.Println("All workers completed")
}

5.2 Channels

package main

import (
    "fmt"
    "time"
)

func producer(ch chan<- int, count int) {
    for i := 0; i < count; i++ {
        ch <- i
        time.Sleep(time.Millisecond * 10)
    }
    close(ch)
}

func main() {
    // Unbuffered channel (synchronous)
    ch1 := make(chan int)
    go func() { ch1 <- 42 }()
    fmt.Println(<-ch1) // 42

    // Buffered channel (async until buffer is full)
    ch2 := make(chan string, 3)
    ch2 <- "first"
    ch2 <- "second"
    ch2 <- "third"
    fmt.Println(<-ch2) // first

    // Iterating over a channel
    numbers := make(chan int, 10)
    go producer(numbers, 5)

    for n := range numbers {
        fmt.Print(n, " ")
    }
    fmt.Println()

    // select: handle multiple channels
    ch3 := make(chan string, 1)
    ch4 := make(chan string, 1)

    go func() {
        time.Sleep(time.Millisecond * 50)
        ch3 <- "channel3"
    }()
    go func() {
        time.Sleep(time.Millisecond * 30)
        ch4 <- "channel4"
    }()

    for i := 0; i < 2; i++ {
        select {
        case msg := <-ch3:
            fmt.Println("received:", msg)
        case msg := <-ch4:
            fmt.Println("received:", msg)
        }
    }
}

5.3 Mutex and Synchronization Primitives

package main

import (
    "fmt"
    "sync"
)

type SafeCounter struct {
    mu sync.Mutex
    v  map[string]int
}

func (c *SafeCounter) Inc(key string) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.v[key]++
}

func (c *SafeCounter) Value(key string) int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.v[key]
}

// RWMutex: concurrent reads, exclusive writes
type Cache struct {
    mu   sync.RWMutex
    data map[string]string
}

func (c *Cache) Set(key, value string) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.data[key] = value
}

func (c *Cache) Get(key string) (string, bool) {
    c.mu.RLock()
    defer c.mu.RUnlock()
    val, ok := c.data[key]
    return val, ok
}

// sync.Once: execute exactly once
var (
    instance *Cache
    once     sync.Once
)

func getInstance() *Cache {
    once.Do(func() {
        instance = &Cache{data: make(map[string]string)}
        fmt.Println("Cache instance created")
    })
    return instance
}

func main() {
    counter := SafeCounter{v: make(map[string]int)}
    var wg sync.WaitGroup

    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            counter.Inc("key")
        }()
    }
    wg.Wait()
    fmt.Println("Counter:", counter.Value("key")) // 1000

    // sync.Map: concurrency-safe map
    var sm sync.Map
    sm.Store("go", "1.21")
    sm.Store("python", "3.12")

    sm.Range(func(key, value interface{}) bool {
        fmt.Printf("%v: %v\n", key, value)
        return true
    })

    c1 := getInstance()
    c2 := getInstance()
    fmt.Println(c1 == c2) // true (same instance)
}

5.4 context.Context

package main

import (
    "context"
    "fmt"
    "time"
)

func doWork(ctx context.Context, id int) error {
    select {
    case <-time.After(time.Millisecond * 200):
        fmt.Printf("Work %d completed\n", id)
        return nil
    case <-ctx.Done():
        fmt.Printf("Work %d cancelled: %v\n", id, ctx.Err())
        return ctx.Err()
    }
}

func fetchData(ctx context.Context, url string) (string, error) {
    if requestID, ok := ctx.Value("requestID").(string); ok {
        fmt.Printf("Request ID: %s, URL: %s\n", requestID, url)
    }
    select {
    case <-time.After(time.Millisecond * 100):
        return "data from " + url, nil
    case <-ctx.Done():
        return "", ctx.Err()
    }
}

func main() {
    // WithCancel
    ctx, cancel := context.WithCancel(context.Background())
    go func() {
        time.Sleep(time.Millisecond * 50)
        cancel()
    }()
    doWork(ctx, 1)

    // WithTimeout
    ctx2, cancel2 := context.WithTimeout(context.Background(), time.Millisecond*150)
    defer cancel2()
    doWork(ctx2, 2)

    // WithDeadline
    deadline := time.Now().Add(time.Millisecond * 300)
    ctx3, cancel3 := context.WithDeadline(context.Background(), deadline)
    defer cancel3()
    doWork(ctx3, 3)

    // WithValue: pass request-scoped data
    ctx4 := context.WithValue(context.Background(), "requestID", "req-001")
    data, err := fetchData(ctx4, "https://api.example.com/users")
    if err == nil {
        fmt.Println("Data:", data)
    }
}

6. Packages and Modules

6.1 Go Modules

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

# Add a dependency
go get github.com/gin-gonic/gin@v1.9.1

# Tidy dependencies
go mod tidy

# Create vendor directory
go mod vendor

Example go.mod file:

module github.com/username/myapp

go 1.21

require (
    github.com/gin-gonic/gin v1.9.1
    github.com/stretchr/testify v1.8.4
)

6.2 Package Structure Best Practices

myapp/
├── cmd/
│   └── server/
│       └── main.go          # entry point
├── internal/                 # not importable by external packages
│   ├── handler/
│   │   └── user.go
│   ├── service/
│   │   └── user.go
│   └── repository/
│       └── user.go
├── pkg/                     # importable library code
│   └── validator/
│       └── validator.go
├── api/                     # API definitions (OpenAPI, Protobuf)
├── config/
│   └── config.go
├── go.mod
└── go.sum

6.3 Key Standard Library Packages

package main

import (
    "fmt"
    "os"
    "strings"
    "strconv"
    "time"
    "io"
    "bytes"
)

func main() {
    // strings package
    s := "  Hello, Go World!  "
    fmt.Println(strings.TrimSpace(s))
    fmt.Println(strings.ToUpper(s))
    fmt.Println(strings.Contains(s, "Go"))
    fmt.Println(strings.Replace(s, "Go", "Golang", 1))
    parts := strings.Split("a,b,c,d", ",")
    fmt.Println(parts)
    fmt.Println(strings.Join(parts, " | "))

    // strconv package
    n, _ := strconv.Atoi("42")
    fmt.Println(n + 1)
    str := strconv.Itoa(100)
    fmt.Println(str + " bottles")
    f, _ := strconv.ParseFloat("3.14", 64)
    fmt.Printf("%.2f\n", f)

    // time package
    now := time.Now()
    fmt.Println(now.Format("2006-01-02 15:04:05"))
    fmt.Println(now.Format(time.RFC3339))
    tomorrow := now.Add(24 * time.Hour)
    fmt.Println(tomorrow.Sub(now))

    t, _ := time.Parse("2006-01-02", "2026-03-17")
    fmt.Println(t.Year(), t.Month(), t.Day())

    // os package
    fmt.Println(os.Getenv("HOME"))

    // io and bytes
    buf := bytes.NewBufferString("Hello")
    buf.WriteString(", World")
    data, _ := io.ReadAll(buf)
    fmt.Println(string(data))
}

7. Generics (Go 1.18+)

7.1 Generic Functions and Types

package main

import "fmt"

// Generic function with type parameter
func Min[T interface{ ~int | ~float64 | ~string }](a, b T) T {
    if a < b {
        return a
    }
    return b
}

func Map[T, U any](slice []T, fn func(T) U) []U {
    result := make([]U, len(slice))
    for i, v := range slice {
        result[i] = fn(v)
    }
    return result
}

func Filter[T any](slice []T, pred func(T) bool) []T {
    var result []T
    for _, v := range slice {
        if pred(v) {
            result = append(result, v)
        }
    }
    return result
}

func Reduce[T, U any](slice []T, initial U, fn func(U, T) U) U {
    acc := initial
    for _, v := range slice {
        acc = fn(acc, v)
    }
    return acc
}

// Generic type
type Stack[T any] struct {
    items []T
}

func (s *Stack[T]) Push(item T) {
    s.items = append(s.items, item)
}

func (s *Stack[T]) Pop() (T, bool) {
    var zero T
    if len(s.items) == 0 {
        return zero, false
    }
    top := s.items[len(s.items)-1]
    s.items = s.items[:len(s.items)-1]
    return top, true
}

func (s *Stack[T]) Len() int {
    return len(s.items)
}

// comparable constraint: allows == operator
func Contains[T comparable](slice []T, item T) bool {
    for _, v := range slice {
        if v == item {
            return true
        }
    }
    return false
}

// Custom type constraint
type Number interface {
    ~int | ~int32 | ~int64 | ~float32 | ~float64
}

func Sum[T Number](nums []T) T {
    var total T
    for _, n := range nums {
        total += n
    }
    return total
}

func main() {
    fmt.Println(Min(3, 5))         // 3
    fmt.Println(Min(3.14, 2.71))   // 2.71
    fmt.Println(Min("apple", "banana")) // apple

    nums := []int{1, 2, 3, 4, 5}
    doubled := Map(nums, func(n int) int { return n * 2 })
    fmt.Println(doubled) // [2 4 6 8 10]

    strs := Map(nums, func(n int) string { return fmt.Sprintf("item_%d", n) })
    fmt.Println(strs)

    evens := Filter(nums, func(n int) bool { return n%2 == 0 })
    fmt.Println(evens) // [2 4]

    total := Reduce(nums, 0, func(acc, n int) int { return acc + n })
    fmt.Println(total) // 15

    var s Stack[string]
    s.Push("first")
    s.Push("second")
    s.Push("third")

    for s.Len() > 0 {
        item, _ := s.Pop()
        fmt.Println(item)
    }

    fmt.Println(Contains([]string{"go", "python", "rust"}, "go")) // true
    fmt.Println(Sum([]float64{1.1, 2.2, 3.3})) // 6.6
}

8. Code Example: REST API Server (net/http)

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "strconv"
    "strings"
    "sync"
)

type User struct {
    ID    int    `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
}

type UserStore struct {
    mu     sync.RWMutex
    users  map[int]User
    nextID int
}

func NewUserStore() *UserStore {
    return &UserStore{
        users:  make(map[int]User),
        nextID: 1,
    }
}

func (s *UserStore) Create(name, email string) User {
    s.mu.Lock()
    defer s.mu.Unlock()
    user := User{ID: s.nextID, Name: name, Email: email}
    s.users[s.nextID] = user
    s.nextID++
    return user
}

func (s *UserStore) GetAll() []User {
    s.mu.RLock()
    defer s.mu.RUnlock()
    users := make([]User, 0, len(s.users))
    for _, u := range s.users {
        users = append(users, u)
    }
    return users
}

func (s *UserStore) GetByID(id int) (User, bool) {
    s.mu.RLock()
    defer s.mu.RUnlock()
    u, ok := s.users[id]
    return u, ok
}

func writeJSON(w http.ResponseWriter, status int, v interface{}) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(status)
    json.NewEncoder(w).Encode(v)
}

func makeUsersHandler(store *UserStore) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        switch r.Method {
        case http.MethodGet:
            users := store.GetAll()
            writeJSON(w, http.StatusOK, users)

        case http.MethodPost:
            var body struct {
                Name  string `json:"name"`
                Email string `json:"email"`
            }
            if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
                writeJSON(w, http.StatusBadRequest, map[string]string{
                    "error": "invalid request body",
                })
                return
            }
            user := store.Create(body.Name, body.Email)
            writeJSON(w, http.StatusCreated, user)

        default:
            http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
        }
    }
}

func makeUserHandler(store *UserStore) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        parts := strings.Split(r.URL.Path, "/")
        if len(parts) < 4 {
            http.Error(w, "Bad Request", http.StatusBadRequest)
            return
        }
        id, err := strconv.Atoi(parts[3])
        if err != nil {
            http.Error(w, "Invalid ID", http.StatusBadRequest)
            return
        }

        user, ok := store.GetByID(id)
        if !ok {
            writeJSON(w, http.StatusNotFound, map[string]string{
                "error": fmt.Sprintf("user %d not found", id),
            })
            return
        }
        writeJSON(w, http.StatusOK, user)
    }
}

func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        log.Printf("%s %s", r.Method, r.URL.Path)
        next.ServeHTTP(w, r)
    })
}

func main() {
    store := NewUserStore()
    store.Create("Alice", "alice@example.com")
    store.Create("Bob", "bob@example.com")

    mux := http.NewServeMux()
    mux.Handle("/api/users", makeUsersHandler(store))
    mux.Handle("/api/users/", makeUserHandler(store))
    mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
        writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
    })

    handler := loggingMiddleware(mux)

    addr := ":8080"
    fmt.Printf("Server started: http://localhost%s\n", addr)
    log.Fatal(http.ListenAndServe(addr, handler))
}

9. Quizzes

Quiz 1: Slice Behavior

What does the following code print?

a := []int{1, 2, 3, 4, 5}
b := a[1:3]
b[0] = 99
fmt.Println(a)

Answer: [1 99 3 4 5]

Explanation: The slice b created by slicing shares the underlying memory with a. b[0] points to the same memory location as a[1], so assigning 99 to b[0] also changes a[1] to 99. Use copy if you need an independent slice.

Quiz 2: Goroutine and Closure Trap

Why is the following code problematic and how should it be fixed?

for i := 0; i < 5; i++ {
    go func() {
        fmt.Println(i)
    }()
}
time.Sleep(time.Second)

Answer: It most likely prints 5 five times (or some unpredictable values).

Explanation: The closure captures the variable i by reference, not by value. By the time the goroutines execute, the loop has already finished and i equals 5. Two correct approaches:

Approach 1 — pass as argument:

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

Approach 2 — shadow with a new variable:

for i := 0; i < 5; i++ {
    i := i // new variable i
    go func() {
        fmt.Println(i)
    }()
}
Quiz 3: Interface and nil

What does err == nil print in the following code?

type MyError struct{ msg string }
func (e *MyError) Error() string { return e.msg }

func getError() error {
    var err *MyError = nil
    return err
}

func main() {
    err := getError()
    fmt.Println(err == nil)
}

Answer: false

Explanation: A Go interface value has two components: type and value. getError() returns an interface that holds the type *MyError with a nil value. Because the interface has a non-nil type component, it is not equal to the nil interface. To correctly return a nil error, return nil directly instead of a typed nil pointer.

Quiz 4: defer Execution Order

What is the output order of the following code?

func main() {
    fmt.Println("A")
    defer fmt.Println("defer 1")
    fmt.Println("B")
    defer fmt.Println("defer 2")
    fmt.Println("C")
    defer fmt.Println("defer 3")
    fmt.Println("D")
}

Answer: A B C D defer 3 defer 2 defer 1

Explanation: defer uses a LIFO (Last In, First Out) stack. Normal statements execute in declaration order. Deferred functions run in reverse order when the function exits — the last deferred runs first.

Quiz 5: Channel Directionality

What is the difference between ch chan<- int and ch <-chan int?

Answer:

  • chan<- int: send-only channel (write only)
  • <-chan int: receive-only channel (read only)

Explanation: Go allows you to restrict channel direction in the type system. This lets the compiler verify at compile time how a function uses a channel. Example:

func send(ch chan<- int, val int) {
    ch <- val   // OK
    // <-ch     // compile error
}

func receive(ch <-chan int) int {
    return <-ch  // OK
    // ch <- 1   // compile error
}

func pipe(in <-chan int, out chan<- int) {
    for v := range in {
        out <- v * 2
    }
}

Specifying channel direction prevents accidentally using a channel in the wrong direction.