COURSE · 8 LESSONS · 100% FREE
🐹

Go Programming

Fast, concurrent, compiled — 8 lessons covering variables, concurrency, error handling, file I/O, testing, CLI and API development.

0Lessons
0Code Examples
SomeProgramming Experience
0%
You've completed 0 of 8 lessons
J/ Next
K/ Prev
Esc Collapse
/ Search
Fundamentals

Variables & Types

Go is statically typed with type inference. Use := for short variable declarations and var for explicit typing.

Go
package main

import "fmt"

func main() {
    // Short declaration with type inference
    name := "Mayank"
    age := 15
    score := 98.5
    active := true

    // Explicit var declaration
    var language string = "Go"
    var version int = 22

    // Multiple declarations
    var (
        x int     = 10
        y float64 = 3.14
        z bool    = true
    )

    fmt.Println(name, age, score, active)
    fmt.Println(language, version)
    fmt.Println(x, y, z)
}

Basic Types

Go
// Integers: int, int8, int16, int32, int64
// Unsigned: uint, uint8, uint16, uint32, uint64
// Floats: float32, float64
// Other: bool, string, byte (uint8), rune (int32)

func main() {
    // Zero values
    var i int       // 0
    var f float64   // 0
    var s string    // ""
    var b bool      // false

    // Constants
    const Pi = 3.14159
    const (
        StatusOK    = 200
        StatusError = 500
    )

    // Type conversion
    x := 42
    y := float64(x)
    z := string(rune(x)) // converts int to Unicode char

    fmt.Println(i, f, s, b)
    fmt.Println(Pi, StatusOK, StatusError)
    fmt.Println(y, z)
}

Functions

Go
// Basic function
func add(a int, b int) int {
    return a + b
}

// Multiple return values
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 swap(a, b string) (first, second string) {
    first = b
    second = a
    return // naked return
}

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

// Functions as values
func apply(f func(int, int) int, a, b int) int {
    return f(a, b)
}

func main() {
    fmt.Println(add(3, 5))
    result, err := divide(10, 3)
    fmt.Println(result, err)

    x, y := swap("hello", "world")
    fmt.Println(x, y)

    fmt.Println(sum(1, 2, 3, 4, 5))
    fmt.Println(apply(add, 10, 20))
}

Packages & Imports

Go
package main

import (
    "fmt"
    "math"
    "strings"
)

func main() {
    // Using math package
    fmt.Println(math.Sqrt(144))  // 12
    fmt.Println(math.Pi)

    // Using strings package
    s := "Hello, Gophers!"
    fmt.Println(strings.ToUpper(s))
    fmt.Println(strings.Contains(s, "Go"))
    fmt.Println(strings.Split("a,b,c", ","))

    // Blank identifier for side effects
    // _ "net/http/pprof"  // registers pprof handlers
}

The fmt Package

Go
func main() {
    // Print functions
    fmt.Print("no newline")
    fmt.Println("with newline")
    fmt.Printf("formatted: %s is %d years old\n", "Mayank", 15)

    // Format verbs
    // %s  string
    // %d  integer
    // %f  float
    // %v  default format
    // %+v struct with field names
    // %#v Go-syntax representation
    // %T  type of value

    type Person struct {
        Name string
        Age  int
    }
    p := Person{"Mayank", 15}
    fmt.Printf("%v\n", p)
    fmt.Printf("%+v\n", p)
    fmt.Printf("%#v\n", p)
    fmt.Printf("Type: %T\n", p)
}
💡
No Unused ImportsGo enforces no unused imports — the compiler rejects code with unused dependencies. This keeps builds clean and fast. Use _ to explicitly discard imported names.
🧪 Quick Check
What is the zero value for a string in Go?

Structs

Structs are Go's primary composite type. They hold named fields and support methods.

Go
package main

import "fmt"

type User struct {
    Name     string
    Email    string
    Age      int
    IsActive bool
}

// Constructor pattern (Go convention)
func NewUser(name, email string, age int) *User {
    return &User{
        Name:     name,
        Email:    email,
        Age:      age,
        IsActive: true,
    }
}

// Method with value receiver
func (u User) Describe() string {
    return fmt.Sprintf("%s (%d) - %s", u.Name, u.Age, u.Email)
}

// Method with pointer receiver (can modify struct)
func (u *User) Deactivate() {
    u.IsActive = false
}

func main() {
    // Named fields
    user := User{Name: "Mayank", Email: "may@dev.com", Age: 15}
    fmt.Println(user.Name)

    // Constructor
    admin := NewUser("Admin", "admin@dev.com", 30)
    fmt.Println(admin.Describe())

    admin.Deactivate()
    fmt.Println(admin.IsActive) // false

    // Anonymous struct
    point := struct{ X, Y int }{3, 7}
    fmt.Println(point)
}

Struct Embedding

Go
type Address struct {
    City    string
    Country string
}

type Employee struct {
    User    // embedded struct — promotes methods
    Address // embedded struct
    Role    string
}

func main() {
    emp := Employee{
        User:    User{Name: "Mayank", Age: 15},
        Address: Address{City: "Nagpur", Country: "India"},
        Role:    "Developer",
    }

    // Promoted fields — accessed as if they belong to Employee
    fmt.Println(emp.Name)    // from User
    fmt.Println(emp.City)    // from Address
    fmt.Println(emp.Describe()) // from User

    // Explicit access
    fmt.Println(emp.User.Name)
    fmt.Println(emp.Address.City)
}

Interfaces

Go
// Interface definition — collection of method signatures
type Shape interface {
    Area() float64
    Perimeter() float64
}

type Circle struct {
    Radius float64
}

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

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

type Rectangle struct {
    Width, Height float64
}

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

func (r Rectangle) Perimeter() float64 {
    return 2 * (r.Width + r.Height)
}

// Accepts any type that implements Shape (implicit satisfaction)
func PrintShapeInfo(s Shape) {
    fmt.Printf("Area: %.2f, Perimeter: %.2f\n", s.Area(), s.Perimeter())
}

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

// Type assertion
func DescribeCircle(s Shape) {
    if c, ok := s.(Circle); ok {
        fmt.Printf("Circle with radius: %.2f\n", c.Radius)
    } else {
        fmt.Println("Not a circle")
    }
}

Interfaces & Polymorphism

Go
type Writer interface {
    Write([]byte) (int, error)
}

type ConsoleWriter struct{}

func (cw ConsoleWriter) Write(data []byte) (int, error) {
    fmt.Print(string(data))
    return len(data), nil
}

// Multiple interfaces satisfied by one type
type ReadWriter interface {
    Reader
    Writer
}

// io.Reader and io.Writer are core Go interfaces
// Many types satisfy them implicitly

func main() {
    var w Writer = ConsoleWriter{}
    w.Write([]byte("Hello Go!\n"))

    // Interface slice
    shapes := []Shape{
        Circle{Radius: 5},
        Rectangle{Width: 4, Height: 6},
    }

    for _, s := range shapes {
        PrintShapeInfo(s)
    }
}
💡
Implicit SatisfactionGo interfaces are satisfied implicitly — no implements keyword needed. This enables "accept interfaces, return structs" as a key Go idiom. Small interfaces (1-2 methods) are preferred.
🧪 Quick Check
How does Go determine if a type implements an interface?
← PrevGo Basics

Goroutines

Goroutines are lightweight threads managed by the Go runtime. Launch thousands without worry.

Go
package main

import (
    "fmt"
    "time"
)

func printNumbers(prefix string) {
    for i := 1; i <= 5; i++ {
        fmt.Printf("%s: %d\n", prefix, i)
        time.Sleep(100 * time.Millisecond)
    }
}

func main() {
    // Launch goroutines
    go printNumbers("A")
    go printNumbers("B")

    // Wait for goroutines to finish
    time.Sleep(1 * time.Second)
    fmt.Println("Done")
}

Channels

Go
// Channels are typed conduits for goroutine communication
func producer(ch chan int) {
    for i := 0; i < 10; i++ {
        ch <- i // send value
    }
    close(ch) // close when done
}

func consumer(ch chan int) {
    for val := range ch { // receive until closed
        fmt.Printf("Received: %d\n", val)
    }
}

func main() {
    ch := make(chan int) // unbuffered channel
    go producer(ch)
    consumer(ch)
}

// Buffered channels
func bufferedExample() {
    ch := make(chan string, 3) // buffer of 3
    ch <- "one"
    ch <- "two"
    ch <- "three"
    // ch <- "four" // blocks here — buffer full

    fmt.Println(<-ch) // "one"
    fmt.Println(<-ch) // "two"
    fmt.Println(<-ch) // "three"
}

Select Statement

Go
func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)

    go func() {
        time.Sleep(1 * time.Second)
        ch1 <- "from channel 1"
    }()

    go func() {
        time.Sleep(2 * time.Second)
        ch2 <- "from channel 2"
    }()

    // Select waits for multiple channel operations
    for i := 0; i < 2; i++ {
        select {
        case msg := <-ch1:
            fmt.Println(msg)
        case msg := <-ch2:
            fmt.Println(msg)
        case <-time.After(3 * time.Second):
            fmt.Println("timeout")
        }
    }
}

sync Package & WaitGroup

Go
import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup

    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            fmt.Printf("Worker %d done\n", id)
        }(i)
    }

    wg.Wait() // blocks until all goroutines finish
    fmt.Println("All workers done")
}

// Mutex for shared state
type SafeCounter struct {
    mu    sync.Mutex
    count int
}

func (c *SafeCounter) Increment() {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.count++
}

func (c *SafeCounter) Get() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.count
}

// sync.Once — execute exactly once
var once sync.Once

func initialize() {
    once.Do(func() {
        fmt.Println("Initialized once")
    })
}

fan-out/fan-in Pattern

Go
func fanOut(input <-chan int, workers int) []<-chan int {
    channels := make([]<-chan int, workers)
    for i := 0; i < workers; i++ {
        channels[i] = process(input)
    }
    return channels
}

func fanIn(channels ...<-chan int) <-chan int {
    var wg sync.WaitGroup
    merged := make(chan int)

    for _, ch := range channels {
        wg.Add(1)
        go func(c <-chan int) {
            defer wg.Done()
            for val := range c {
                merged <- val
            }
        }(ch)
    }

    go func() {
        wg.Wait()
        close(merged)
    }()

    return merged
}
💡
Share Memory by Communicating"Don't communicate by sharing memory; share memory by communicating." This is Go's concurrency mantra. Channels are preferred over mutexes when possible. Use go vet to detect race conditions.
← PrevStructs & Interfaces

The Error Interface

Go
package main

import (
    "errors"
    "fmt"
)

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

func main() {
    result, err := divide(10, 0)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    fmt.Println("Result:", result)
}

Custom Error Types

Go
// Custom error with context
type ValidationError struct {
    Field   string
    Message string
}

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

// Sentinel errors
var (
    ErrNotFound     = errors.New("not found")
    ErrUnauthorized = errors.New("unauthorized")
    ErrForbidden    = errors.New("forbidden")
)

func findUser(id int) (string, error) {
    if id == 0 {
        return "", ErrNotFound
    }
    return "Mayank", nil
}

func main() {
    _, err := findUser(0)
    if errors.Is(err, ErrNotFound) {
        fmt.Println("User not found")
    }

    // Custom error
    err = &ValidationError{Field: "email", Message: "required"}
    fmt.Println(err)
}

Error Wrapping (Go 1.13+)

Go
import "fmt"

func readConfig(path string) error {
    file, err := os.Open(path)
    if err != nil {
        // %w wraps the error — preserves the original chain
        return fmt.Errorf("reading config: %w", err)
    }
    defer file.Close()

    // parse config...
    return nil
}

func main() {
    err := readConfig("/nonexistent/config.yaml")
    if err != nil {
        fmt.Println(err)
        // output: reading config: open /nonexistent/config.yaml: no such file or directory

        // Check wrapped errors
        var pathErr *os.PathError
        if errors.As(err, &pathErr) {
            fmt.Println("Path:", pathErr.Path)
        }

        // Check for specific error in chain
        if errors.Is(err, os.ErrNotExist) {
            fmt.Println("Config file not found")
        }
    }
}

Common Patterns

Go
// Pattern 1: Check and return
func process() error {
    data, err := fetchData()
    if err != nil {
        return fmt.Errorf("process: %w", err)
    }

    result, err := transform(data)
    if err != nil {
        return fmt.Errorf("process: %w", err)
    }

    return save(result)
}

// Pattern 2: defer for cleanup
func readFile(path string) error {
    f, err := os.Open(path)
    if err != nil {
        return err
    }
    defer f.Close() // guaranteed cleanup

    // process file...
    return nil
}

// Pattern 3: Custom error checking
func IsNotFoundError(err error) bool {
    return errors.Is(err, ErrNotFound)
}

// Pattern 4: Error aggregation
type MultiError struct {
    Errors []error
}

func (e *MultiError) Error() string {
    msgs := make([]string, len(e.Errors))
    for i, err := range e.Errors {
        msgs[i] = err.Error()
    }
    return strings.Join(msgs, "; ")
}

func (e *MultiError) Unwrap() []error {
    return e.Errors
}
⚠️
No Try/Catch in GoGo does not have try/catch. The idiomatic pattern is if err != nil { return err }. Always return errors with context using fmt.Errorf. Check error identity with errors.Is and type with errors.As.
← PrevConcurrency
Intermediate

File Operations

Go
package main

import (
    "fmt"
    "os"
)

func main() {
    // Write file
    err := os.WriteFile("data.txt", []byte("Hello, Go!\n"), 0644)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }

    // Read file
    data, err := os.ReadFile("data.txt")
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    fmt.Print(string(data))

    // Using os.Open with io package
    f, err := os.Open("data.txt")
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    defer f.Close()

    // Read with io.ReadAll
    content, err := io.ReadAll(f)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    fmt.Print(string(content))
}

Working with Directories

Go
import (
    "fmt"
    "os"
    "path/filepath"
)

func main() {
    // Create directory
    os.MkdirAll("data/logs", 0755)

    // List directory contents
    entries, err := os.ReadDir(".")
    if err != nil {
        fmt.Println(err)
        return
    }

    for _, entry := range entries {
        info, _ := entry.Info()
        fmt.Printf("%-10s %s\n", entry.Name(), info.Mode())
    }

    // Walk directory tree
    filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
        if err != nil {
            return err
        }
        fmt.Println(path)
        return nil
    })

    // Get file info
    stat, _ := os.Stat("data.txt")
    fmt.Println("Size:", stat.Size())
    fmt.Println("Mode:", stat.Mode())
    fmt.Println("Modified:", stat.ModTime())
}

HTTP Client

Go
import (
    "fmt"
    "io"
    "net/http"
)

func main() {
    // Simple GET request
    resp, err := http.Get("https://api.github.com/users/mayank-dev-15")
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    defer resp.Body.Close()

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }

    fmt.Println("Status:", resp.Status)
    fmt.Println("Body:", string(body))
}

// POST request with JSON
func postJSON() {
    payload := `{"name": "Mayank", "age": 15}`
    resp, err := http.Post(
        "https://api.example.com/users",
        "application/json",
        strings.NewReader(payload),
    )
    if err != nil {
        fmt.Println(err)
        return
    }
    defer resp.Body.Close()
}

TCP Server

Go
import (
    "bufio"
    "fmt"
    "net"
    "strings"
)

func handleConnection(conn net.Conn) {
    defer conn.Close()

    scanner := bufio.NewScanner(conn)
    for scanner.Scan() {
        msg := strings.TrimSpace(scanner.Text())
        fmt.Printf("Received: %s\n", msg)
        conn.Write([]byte("Echo: " + msg + "\n"))
    }
}

func main() {
    listener, err := net.Listen("tcp", ":8080")
    if err != nil {
        fmt.Println(err)
        return
    }
    defer listener.Close()

    fmt.Println("Server listening on :8080")
    for {
        conn, err := listener.Accept()
        if err != nil {
            fmt.Println(err)
            continue
        }
        go handleConnection(conn) // handle each connection concurrently
    }
}

UDP Server

Go
import (
    "fmt"
    "net"
)

func main() {
    addr, _ := net.ResolveUDPAddr("udp", ":9090")
    conn, _ := net.ListenUDP("udp", addr)
    defer conn.Close()

    buf := make([]byte, 1024)
    for {
        n, remoteAddr, _ := conn.ReadFromUDP(buf)
        fmt.Printf("Received from %s: %s\n", remoteAddr, string(buf[:n]))
        conn.WriteToUDP([]byte("ACK"), remoteAddr)
    }
}
Resource CleanupAlways close resources with defer. Use io.ReadAll (not ioutil.ReadAll) for reading entire responses. For large files, use streaming with bufio.Scanner to avoid loading everything into memory.
← PrevError Handling

Basic Tests

Go has a built-in testing framework. Files end with _test.go.

Go
// math.go
package math

func Add(a, b int) int {
    return a + b
}

func Multiply(a, b int) int {
    return a * b
}

func Divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("division by zero")
    }
    return a / b, nil
}
Go
// math_test.go
package math

import "testing"

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

func TestMultiply(t *testing.T) {
    tests := []struct {
        name     string
        a, b     int
        expected int
    }{
        {"positive", 3, 4, 12},
        {"zero", 5, 0, 0},
        {"negative", -2, 3, -6},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            result := Multiply(tt.a, tt.b)
            if result != tt.expected {
                t.Errorf("Multiply(%d, %d) = %d; want %d",
                    tt.a, tt.b, result, tt.expected)
            }
        })
    }
}

func TestDivide(t *testing.T) {
    result, err := Divide(10, 2)
    if err != nil {
        t.Fatalf("unexpected error: %v", err)
    }
    if result != 5 {
        t.Errorf("Divide(10, 2) = %f; want 5", result)
    }

    _, err = Divide(10, 0)
    if err == nil {
        t.Error("expected error for division by zero")
    }
}

Table-Driven Tests

Go
func TestFibonacci(t *testing.T) {
    tests := []struct {
        input    int
        expected int
    }{
        {0, 0},
        {1, 1},
        {2, 1},
        {5, 5},
        {10, 55},
    }

    for _, tt := range tests {
        t.Run(fmt.Sprintf("fib(%d)", tt.input), func(t *testing.T) {
            result := Fibonacci(tt.input)
            if result != tt.expected {
                t.Errorf("Fibonacci(%d) = %d; want %d",
                    tt.input, result, tt.expected)
            }
        })
    }
}

Benchmarks

Go
func BenchmarkAdd(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Add(1, 2)
    }
}

func BenchmarkFibonacci(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Fibonacci(20)
    }
}

// Run: go test -bench=. -benchmem
// Output:
// BenchmarkAdd-8     1000000000   0.25 ns/op   0 B/op   0 allocs/op
// BenchmarkFibonacci-8   50000   32000 ns/op  16 B/op   1 allocs/op

Fuzzing (Go 1.18+)

Go
func FuzzReverse(f *testing.F) {
    // Seed corpus
    f.Add("hello")
    f.Add("Go")
    f.Add("")

    f.Fuzz(func(t *testing.T, s string) {
        rev := Reverse(s)
        doubleRev := Reverse(rev)
        if s != doubleRev {
            t.Errorf("double reverse mismatch: %s vs %s", s, doubleRev)
        }
    })
}

// Run: go test -fuzz=FuzzReverse -fuzztime=10s

Test Helpers & Subtests

Go
// Helper function
func assertEqual(t *testing.T, got, want int) {
    t.Helper() // marks as test helper — better error messages
    if got != want {
        t.Errorf("got %d, want %d", got, want)
    }
}

// Parallel tests
func TestWithTimeout(t *testing.T) {
    t.Parallel() // run in parallel with other tests

    result := slowOperation()
    assertEqual(t, result, 42)
}

// Test cleanup
func TestWithResource(t *testing.T) {
    tmpFile := createTempFile(t)
    t.Cleanup(func() {
        os.Remove(tmpFile)
    })

    // test using tmpFile...
}
Go Testing Best PracticesRun tests with go test ./.... Use -race flag to detect race conditions. Table-driven tests are the Go standard — keep test cases as data. Use t.Helper() in helper functions for clearer error lines.
← PrevFile & Network I/O
Advanced

os.Args

Go
package main

import (
    "fmt"
    "os"
)

func main() {
    // os.Args[0] is the program name
    if len(os.Args) < 2 {
        fmt.Println("Usage: program ")
        os.Exit(1)
    }

    command := os.Args[1]
    args := os.Args[2:]

    switch command {
    case "greet":
        if len(args) > 0 {
            fmt.Printf("Hello, %s!\n", args[0])
        } else {
            fmt.Println("Hello, World!")
        }
    case "version":
        fmt.Println("v1.0.0")
    default:
        fmt.Printf("Unknown command: %s\n", command)
        os.Exit(1)
    }
}

flag Package

Go
package main

import (
    "flag"
    "fmt"
    "strings"
)

func main() {
    // Define flags
    name := flag.String("name", "World", "Name to greet")
    count := flag.Int("count", 1, "Number of greetings")
    upper := flag.Bool("upper", false, "Uppercase output")
    output := flag.String("output", "stdout", "Output file")

    // Custom flag type
    var colors flag.Value
    colors = &stringSliceFlag{value: []string{"red", "blue"}}
    flag.Var(colors, "colors", "Colors to use")

    // Parse flags
    flag.Parse()

    // Usage
    for i := 0; i < *count; i++ {
        msg := fmt.Sprintf("Hello, %s!", *name)
        if *upper {
            msg = strings.ToUpper(msg)
        }
        fmt.Fprintln(os.Stdout, msg)
    }
}

// String slice flag
type stringSliceFlag struct {
    value []string
}

func (s *stringSliceFlag) String() string {
    return strings.Join(s.value, ",")
}

func (s *stringSliceFlag) Set(val string) error {
    s.value = strings.Split(val, ",")
    return nil
}

// Run:
// ./cli -name Mayank -count 3 -upper
// ./cli -name="Go Developers" -count=5

Cobra CLI Framework

Go
// go get github.com/spf13/cobra
package main

import (
    "fmt"
    "os"
    "github.com/spf13/cobra"
)

func main() {
    var rootCmd = &cobra.Command{
        Use:   "myapp",
        Short: "My awesome CLI tool",
        Long:  "A longer description of your application",
    }

    // Subcommand
    var serveCmd = &cobra.Command{
        Use:   "serve [port]",
        Short: "Start the server",
        Args:  cobra.MaximumNArgs(1),
        Run: func(cmd *cobra.Command, args []string) {
            port := "8080"
            if len(args) > 0 {
                port = args[0]
            }
            fmt.Printf("Server starting on :%s\n", port)
        },
    }

    var greetCmd = &cobra.Command{
        Use:   "greet [name]",
        Short: "Greet someone",
        Args:  cobra.ExactArgs(1),
        Run: func(cmd *cobra.Command, args []string) {
            name := args[0]
            upper, _ := cmd.Flags().GetBool("upper")
            if upper {
                name = strings.ToUpper(name)
            }
            fmt.Printf("Hello, %s!\n", name)
        },
    }

    greetCmd.Flags().BoolP("upper", "u", false, "Uppercase output")

    rootCmd.AddCommand(serveCmd, greetCmd)
    rootCmd.Execute()
}

Output Formatting

Go
import (
    "encoding/json"
    "fmt"
    "os"
    "text/tabwriter"
)

// Pretty table output
func printTable(data [][]string) {
    w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
    for _, row := range data {
        fmt.Fprintln(w, strings.Join(row, "\t"))
    }
    w.Flush()
}

// JSON output
func printJSON(v interface{}) {
    encoder := json.NewEncoder(os.Stdout)
    encoder.SetIndent("", "  ")
    encoder.Encode(v)
}

// Colored output (ANSI codes)
func colorPrint(color, text string) {
    codes := map[string]string{
        "red":    "\033[31m",
        "green":  "\033[32m",
        "yellow": "\033[33m",
        "blue":   "\033[34m",
        "reset":  "\033[0m",
    }
    fmt.Printf("%s%s%s\n", codes[color], text, codes["reset"])
}

Progress Bars & Spinners

Go
// Simple progress bar
func progressBar(total int) {
    for i := 0; i <= total; i++ {
        percent := float64(i) / float64(total) * 100
        bar := strings.Repeat("=", i) + strings.Repeat("-", total-i)
        fmt.Printf("\r[%s] %.0f%%", bar, percent)
        time.Sleep(50 * time.Millisecond)
    }
    fmt.Println()
}

// Spinner for async operations
func spinner(msg string) {
    chars := []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}
    for i := 0; ; i++ {
        fmt.Printf("\r%s %s", chars[i%len(chars)], msg)
        time.Sleep(100 * time.Millisecond)
    }
}
ℹ️
CLI ToolingUse flag for simple CLIs and cobra for complex multi-command tools. Always provide --help text. Write output to stdout (not stderr) unless it's errors. Use os.Exit(1) for error codes.
← PrevTesting in Go

Basic HTTP Server

Go
package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
)

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

var users = []User{
    {ID: 1, Name: "Mayank", Email: "may@dev.com"},
    {ID: 2, Name: "GoBot", Email: "bot@dev.com"},
}

func main() {
    http.HandleFunc("/", homeHandler)
    http.HandleFunc("/users", usersHandler)
    http.HandleFunc("/users/", userHandler)

    fmt.Println("Server running on :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

func homeHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Welcome to the Go API")
}

func usersHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")

    switch r.Method {
    case http.MethodGet:
        json.NewEncoder(w).Encode(users)

    case http.MethodPost:
        var user User
        if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
            http.Error(w, err.Error(), http.StatusBadRequest)
            return
        }
        user.ID = len(users) + 1
        users = append(users, user)
        w.WriteHeader(http.StatusCreated)
        json.NewEncoder(w).Encode(user)

    default:
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
    }
}

Middleware

Go
// Logging middleware
func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        next.ServeHTTP(w, r)
        log.Printf("%s %s %s %v", r.RemoteAddr, r.Method, r.URL.Path, time.Since(start))
    })
}

// CORS middleware
func corsMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Access-Control-Allow-Origin", "*")
        w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
        w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")

        if r.Method == http.MethodOptions {
            w.WriteHeader(http.StatusOK)
            return
        }

        next.ServeHTTP(w, r)
    })
}

// Auth middleware
func authMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        token := r.Header.Get("Authorization")
        if token == "" {
            http.Error(w, "Unauthorized", http.StatusUnauthorized)
            return
        }
        // validate token...
        next.ServeHTTP(w, r)
    })
}

// Chain middlewares
func setupRoutes() http.Handler {
    mux := http.NewServeMux()
    mux.HandleFunc("/api/users", usersHandler)

    var handler http.Handler = mux
    handler = authMiddleware(handler)
    handler = corsMiddleware(handler)
    handler = loggingMiddleware(handler)

    return handler
}

chi Router

Go
// go get github.com/go-chi/chi/v5
import (
    "github.com/go-chi/chi/v5"
    "github.com/go-chi/chi/v5/middleware"
)

func main() {
    r := chi.NewRouter()

    // Built-in middleware
    r.Use(middleware.Logger)
    r.Use(middleware.Recoverer)
    r.Use(middleware.URLFormat)
    r.Use(middleware.RealIP)

    // Routes
    r.Route("/api", func(r chi.Router) {
        r.Route("/users", func(r chi.Router) {
            r.Get("/", listUsers)
            r.Post("/", createUser)

            r.Route("/{userID}", func(r chi.Router) {
                r.Get("/", getUser)
                r.Put("/", updateUser)
                r.Delete("/", deleteUser)
            })
        })
    })

    fmt.Println("Server running on :8080")
    log.Fatal(http.ListenAndServe(":8080", r))
}

func getUser(w http.ResponseWriter, r *http.Request) {
    userID := chi.URLParam(r, "userID")
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(map[string]string{"id": userID})
}

JSON Encoding & Decoding

Go
type Article struct {
    ID        int       `json:"id"`
    Title     string    `json:"title"`
    Content   string    `json:"content,omitempty"`   // omitempty omits zero values
    Author    string    `json:"-"`
    CreatedAt time.Time `json:"created_at"`
    Tags      []string  `json:"tags,omitempty"`
}

func createArticle(w http.ResponseWriter, r *http.Request) {
    var article Article

    // Decode request body
    decoder := json.NewDecoder(r.Body)
    decoder.DisallowUnknownFields() // reject unknown fields
    if err := decoder.Decode(&article); err != nil {
        http.Error(w, "Invalid JSON: "+err.Error(), http.StatusBadRequest)
        return
    }

    // Set server-side fields
    article.ID = len(articles) + 1
    article.CreatedAt = time.Now()
    articles = append(articles, article)

    // Encode response
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusCreated)
    encoder := json.NewEncoder(w)
    encoder.SetIndent("", "  ")
    encoder.Encode(article)
}

// Custom JSON response helper
func respondJSON(w http.ResponseWriter, status int, data interface{}) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(status)
    json.NewEncoder(w).Encode(data)
}

func respondError(w http.ResponseWriter, status int, message string) {
    respondJSON(w, status, map[string]string{"error": message})
}

REST API Pattern

Go
// Full REST API skeleton
type API struct {
    router  *chi.Mux
    store   *Store
    port    string
}

func NewAPI(port string) *API {
    api := &API{
        router: chi.NewRouter(),
        store:  NewStore(),
        port:   port,
    }
    api.setupRoutes()
    return api
}

func (a *API) setupRoutes() {
    a.router.Use(middleware.Logger)
    a.router.Use(middleware.Recoverer)

    a.router.Route("/api/v1", func(r chi.Router) {
        r.Route("/users", func(r chi.Router) {
            r.Get("/", a.ListUsers)
            r.Post("/", a.CreateUser)
            r.Get("/{id}", a.GetUser)
            r.Put("/{id}", a.UpdateUser)
            r.Delete("/{id}", a.DeleteUser)
        })
    })
}

func (a *API) ListUsers(w http.ResponseWriter, r *http.Request) {
    users := a.store.ListUsers()
    respondJSON(w, http.StatusOK, users)
}

func (a *API) Start() error {
    addr := ":" + a.port
    fmt.Printf("API server running on %s\n", addr)
    return http.ListenAndServe(addr, a.router)
}

func main() {
    api := NewAPI("8080")
    log.Fatal(api.Start())
}
⚠️
Production APIsUse chi or gin for routing in production. Always validate input, set proper HTTP status codes, and return consistent JSON error responses. Use context.Context for request-scoped values and cancellation.
← PrevBuilding CLIs

📚 Resources & Further Learning

📖
Official Go Docs
The official Go documentation, tutorials, and reference materials.
go.dev/doc →
📘
Effective Go
Tips for writing clear, idiomatic Go code from the Go team.
go.dev/doc/effective_go →
🎯
Go by Example
Hands-on introduction to Go using examples with runnable code.
gobyexample.com →
🧪
Go Tour
The official interactive tour of Go concepts and syntax.
go.dev/learn →
Go GitHub
The Go source code, issues, and contributions on GitHub.
github.com/golang/go →
📦
Go Package Docs
Searchable documentation for all public Go packages.
pkg.go.dev →
AI
Go Tutor
ZenMux · GLM 4.7 Flash
Ask me anything about Go! I can help with goroutines, interfaces, error handling, APIs, or explain any concept from the lessons above.