Frontend Labs
⋯

11. Packages and modules

11.1 Visibility is capitalization

There's no public/private keyword. An identifier starting with an uppercase letter is exported (visible outside the package); lowercase is package-private. This single rule replaces access modifiers entirely.

11.2 One package per directory, package name ≠ import path segment always

myapp/
  go.mod              module github.com/kumaran/myapp
  config/
    config.go          package config
  storage/
    storage.go          package storage
    memory.go           package storage  (same dir, same package)
  server/
    server.go            package server
  internal/
    auth/
      auth.go            package auth — importable ONLY from within myapp

internal/ is a compiler-enforced convention: any package under a directory named internal can only be imported by code rooted at the parent of that internal directory. It's Go's answer to "private to this module, but shared across several of its own packages."

11.3 init() and import cycles

Every package can define one or more init() functions, run automatically before main(), in dependency order — used for registering drivers (database/sql drivers register themselves this way) but easy to overuse into "spooky action at a distance." Prefer explicit initialization (a New() constructor) unless you're building a plugin-registration pattern. Go also forbids import cycles at compile time — if package a imports b and b imports a, the build fails immediately, forcing you to extract the shared piece into a third package.

11.4 Real-world example

Day 9's refactor of the Week 1 project into config/, storage/, server/, health/ packages is the shape of nearly every non-trivial Go service: each package owns one responsibility, server depends on config and storage but not vice versa, and the dependency graph is a DAG you can read straight off the import statements — no hidden coupling through global state.


Generated from real-life-golang-guide.md · 8 Oct 2026 16:36