Packages and Visibility

As a program grows, you split it into packages: folders of Go files that each own one job, such as pricing, storage or handlers. A package decides what other packages may use simply by capitalisation. This lesson builds a small multi-package module, explains exported and unexported names, import paths, the special internal folder, and how to name and structure packages well.

One folder, one package

Every .go file in a folder must declare the same package name, and that package's import path is the module path plus the folder path. Here's a module with two packages besides main:

TXT
shop/
├── go.mod              module example.com/shop
├── main.go             package main
├── cart/
│   └── cart.go         package cart      → import "example.com/shop/cart"
└── internal/
    └── money/
        └── money.go    package money     → import "example.com/shop/internal/money"
GO.MOD
module example.com/shop

go 1.27
GO internal/money/money.go
// Package money formats and calculates amounts held in cents.
package money

import "fmt"

// Cents is an amount of money in the smallest currency unit.
type Cents int64

// String formats the amount as euros, for example "€12.50".
func (c Cents) String() string {
	return fmt.Sprintf("€%d.%02d", c/100, c%100)
}

// WithTax adds tax at the given percentage, rounding to the nearest cent.
func WithTax(c Cents, percent int64) Cents {
	return c + roundDiv(int64(c)*percent, 100)
}

// roundDiv is unexported: only package money can call it.
func roundDiv(a, b int64) Cents {
	return Cents((a + b/2) / b)
}
GO cart/cart.go
// Package cart holds the items a customer is buying.
package cart

import "example.com/shop/internal/money"

type item struct { // unexported type
	name  string
	price money.Cents
	qty   int
}

// Cart is a shopping cart. The zero value is an empty cart, ready to use.
type Cart struct {
	items []item
}

// Add puts qty units of a product into the cart.
func (c *Cart) Add(name string, price money.Cents, qty int) {
	c.items = append(c.items, item{name, price, qty})
}

// Total returns the cart's total before tax.
func (c *Cart) Total() money.Cents {
	var t money.Cents
	for _, it := range c.items {
		t += it.price * money.Cents(it.qty)
	}
	return t
}
GO main.go
package main

import (
	"fmt"

	"example.com/shop/cart"
	"example.com/shop/internal/money"
)

func main() {
	var c cart.Cart
	c.Add("Notebook", 350, 4)
	c.Add("Pen", 120, 10)

	total := c.Total()
	fmt.Println("subtotal:", total)
	fmt.Println("with 23% tax:", money.WithTax(total, 23))
}
$
go run .
Output
subtotal: €26.00
with 23% tax: €31.98

Code refers to another package's names through the package name — the last element of its import path — as in cart.Cart and money.WithTax. Packages can't import each other in a circle; if two packages need each other, that's a sign some code belongs in a third.

Exported and unexported

A name that starts with a capital letter is exported: other packages can use it. Anything else is unexported and private to its package. This applies to everything — functions, types, struct fields, methods, constants and variables. Trying to use an unexported name from outside fails to compile:

GO main.go
package main

import (
	"fmt"

	"example.com/shop/internal/money"
)

func main() {
	fmt.Println(money.roundDiv(7, 2))
}
Compiler error
./main.go:10:20: name roundDiv not exported by package money

Export as little as you can. A small public surface is easier to document, and you're free to change everything unexported without breaking anyone. Notice that cart.Cart's items field is unexported: callers must use Add, so the cart can never get into an invalid state.

The internal folder

A package inside a folder named internal can be imported only by code in the folder that contains internal (and its subfolders). Here, anything in example.com/shop/… may import internal/money, but another module can't. Use internal for helpers you need to share across your own packages without making them part of your public API.

Import forms

GO
import (
	"fmt"                        // standard library
	"example.com/shop/cart"      // your module's package

	str "strings"                // alias: use as str.ToUpper
	_ "image/png"                // blank import: run the package's init only
)

Aliases resolve name clashes, such as two packages both called rand. Blank imports are for packages that register themselves when loaded — image decoders and database drivers are the usual examples. Dot imports (. "fmt") exist but are discouraged.

Naming packages

  • Short, lowercase, single words: cart, money, httputil — no underscores or mixedCaps.
  • Name the package for what it provides, and avoid repeating that name in exported identifiers: cart.New rather than cart.NewCart, money.Cents rather than money.MoneyCents. Callers always see both together.
  • Avoid catch-all names like utils, common or helpers; they say nothing and attract unrelated code.
  • The folder name and package name should match (except for main).

Structuring a project

Start with a single package and split only when a clear boundary appears. A common layout for a module that builds one program:

TXT
myapp/
├── go.mod
├── main.go            (or cmd/myapp/main.go when there are several programs)
├── internal/
│   ├── store/         database access
│   └── web/           HTTP handlers
└── README.md

There's no required layout. Avoid copying a deep folder structure from a template before you need it; flat and simple is idiomatic Go.

Package initialisation

When a program starts, each imported package is initialised before the package that imports it: package-level variables are set, then its init functions run. Every package is initialised only once, however many places import it. Then main.main runs.

Try it yourself

In your own module, create a package greet in a greet folder with an exported Hello(name string) string and an unexported helper, then call greet.Hello from main.go. Try calling the helper from main and read the error.

Try it yourself · greet/greet.go
Edit the code, copy it into greet/greet.go in a new folder and run it with go run greet/greet.go (Go 1.27 or later). How to install Go
Next: handling errors.