Bits.

Closures in Go: A Beginner-Friendly Introduction

Simple, practical examples that show how to keep small state local and safe.

Last updated
Sept 2025

A closure is just a function that keeps access to variables from where it was created. This lets you build small helpers with private state without exposing internal variables.

1) Minimal example — a counter

The returned function remembers the variable i. Each call increments it.

GO
// counter.go
package main

import "fmt"

func counter(start int) func() int {
    i := start
    return func() int {
        i++
        return i
    }
}

func main() {
    c := counter(0)
    fmt.Println(c()) // 1
    fmt.Println(c()) // 2
}

Here, counter returns a function that still has access to i. That’s the entire idea of closures.

2) A practical API — returning Inc + Peek

Instead of exposing the value, return a clean API. Here we expose Inc and Peek while keeping the counter private.

GO
// encapsulate.go
package main

import "fmt"

type counterAPI struct {
    Inc  func() int
    Peek func() int
}

func newCounter(start int) counterAPI {
    i := start

    inc := func() int {
        i++
        return i
    }

    peek := func() int {
        return i
    }

    return counterAPI{Inc: inc, Peek: peek}
}

func main() {
    c := newCounter(5)
    fmt.Println(c.Peek()) // 5
    fmt.Println(c.Inc())  // 6
    fmt.Println(c.Peek()) // 6
}

This keeps internal state hidden and forces callers to use a consistent API.

Concurrency note

These examples are not concurrency-safe. Multiple goroutines updating i will race. To fix that, wrap a sync.Mutex inside the factory.

When closures make sense

Use closures for small helpers and factories. For bigger APIs or anything requiring interfaces, prefer a named struct with methods.