Skip to content

Intermediate · Packages, testing and generics · topic 21 of 46 · 35 min

Packages, visibility and project layout

Explanation

A package is one folder of .go files that share a package name. Names that start with a capital letter are exported (usable from other packages); lowercase names are private. A folder called internal can only be imported by code next to or above it. Start with a simple layout and add folders only when the project needs them.

Why split code into packages?

A 3,000-line main.go is hard to read, test and share. Packages group related code: everything about invoices in one package, everything about the database in another. Each package has a clear job and a small public surface (the names other packages can use).

One folder, one package

  • All .go files in one folder must have the same package name (test files may also use name_test).
  • The package name is usually the last part of the folder path: folder internal/invoice has package invoice.
  • Package names are short, lowercase, one word, no underscores: invoice, store, httpapi. Avoid vague names like util, common or helpers.
  • You import a package by its full path: the module path plus the folder, for example "example.com/billing/internal/invoice".
output
billing/
├── go.mod                 module example.com/billing
├── main.go                package main
└── internal/
    └── invoice/
        ├── invoice.go     package invoice
        └── invoice_test.go
A small but real layout.
go
// internal/invoice/invoice.go
package invoice

// GSTPercent is exported: other packages can read it.
const GSTPercent = 18

// Total is exported. It returns the amount with GST, in paise.
func Total(paise int64) int64 {
	return paise + gst(paise)
}

// gst is unexported: only code in package invoice can call it.
func gst(paise int64) int64 {
	return paise * GSTPercent / 100
}
Comments that start with the name before an exported identifier become its documentation (go doc and pkg.go.dev show them).
go
// main.go
package main

import (
	"fmt"

	"example.com/billing/internal/invoice"
)

func main() {
	fmt.Println(invoice.Total(10000)) // 11800
	// invoice.gst(100) // compile error: gst not exported
}
You use exported names as packagename.Name.

Visibility: just the first letter

Go has no public, private or protected keywords. An identifier (a name of a type, function, variable, constant, field or method) that starts with an upper-case letter is exported. Anything else is visible only inside its own package. The same rule applies to struct fields, which matters for JSON (module 7).

internal/: a wall the compiler enforces

Code inside a folder named internal can only be imported by packages rooted at the parent of internal. In billing/internal/invoice, only packages under billing/ can import it. Other modules cannot, even if your repository is public. Use internal/ for everything you do not want others to depend on.

A sensible layout, without over-engineering

Project sizeLayout
Small tool or learning projectgo.mod and main.go (plus a few more .go files) in one folder
Service with some logicmain.go at the root, packages under internal/
Several binaries (api, worker, cli)cmd/api/main.go, cmd/worker/main.go, shared code in internal/
Careful: A popular GitHub repository called "project-layout" shows dozens of folders (pkg/, api/, build/, ...). It is NOT an official Go standard, and copying it into a tiny project only adds noise. The Go team's own guide, "Organizing a Go module", recommends starting simple.

Other rules worth knowing

  • Import cycles are not allowed: if package a imports b, then b cannot import a. Fix it by moving the shared piece into a third package or by using an interface.
  • func init() runs automatically before main, after package-level variables are set. Keep init small; hidden start-up work is hard to test.
  • A blank import, import _ "github.com/jackc/pgx/v5/stdlib", runs a package's init only. Database drivers use this to register themselves (module 8).
  • You can rename an import: import ex "example.com/billing/internal/invoice". Use it only to fix name clashes.
Tip: Run go doc ./internal/invoice to see the documentation of your own package in the terminal, built from your comments.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Packages, visibility and project layout · 1/7

Packages

As projects grow, we split code into packages. Each package is one folder with one job.

Prefer a video by a teacher? Find free videos on “golang packages exported names internal folder project structure” on YouTube ↗

In real life: Splitting an expense tracker

A student's expense tracker started as one 900-line main.go. She splits it into internal/expense (types and rules), internal/store (saving to a file) and main.go (reading commands). Tests for the expense rules now run without touching files, and a friend can read each package in five minutes.

output
expenses/
├── go.mod                   module example.com/expenses
├── main.go                  wires everything together
└── internal/
    ├── expense/
    │   ├── expense.go       type Expense, func Validate, func Total
    │   └── expense_test.go
    └── store/
        └── file.go          func Load(path) / func Save(path, list)
Each package has one job; main only connects them.

Practice (0/4 done)

Task 1 · Question

Which function can another package call?

Task 2 · Question

Module example.com/shop has shop/internal/pay. Can module example.com/other import it?

Task 3 · Question

Package a imports b, and you add an import of a inside b. What happens?

Task 4 · Practice

Create a module example.com/marks with a package internal/grade that exports func Letter(marks int) string and keeps a helper unexported. Call grade.Letter from main.go.

Quick quiz

Question 1

1.What decides whether a Go name is exported?

Question 2

2.What is the usual package name for files in folder internal/store?

Question 3

3.Why might you write import _ "some/driver"?

Question 4

4.Is the popular "project-layout" repository an official Go standard?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

What decides whether a Go name is exported?

Answer: Whether its first letter is upper-case

Upper-case first letter means exported.

What is the usual package name for files in folder internal/store?

Answer: store

By convention the package name matches the last part of the folder path.

Why might you write import _ "some/driver"?

Answer: To run its init (for example to register a database driver) without using its names

A blank import is for side effects only.

Is the popular "project-layout" repository an official Go standard?

Answer: No; the Go team recommends starting simple

It is a community repo, not an official standard.

Which modifier allows access from subclasses in other packages but not from unrelated classes there?Related

Answer: protected

protected adds subclasses to package access.

From Packages and access modifiers

In import formatPrice, { addGst } from './money.js'; which one is the default export?Related

Answer: formatPrice

The name outside the braces is the default export. Names inside { } are named exports.

From Modules, npm and Vite

Which function can be used from another package?Related

Answer: CalculateTax

Names starting with a capital letter are exported.

From Functions and multiple return values

How many default exports can a module have?Related

Answer: At most one

One default export per module; named exports are unlimited.

From Modules: import and export

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 4 more topics in “Packages, testing and generics” before its exam: Dependencies with Go modules, Testing with go test, Benchmarks and fuzz tests, Generics: type parameters and constraints.
  2. Module 5 exam: Packages, testing and generics · 14 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, Concurrency: goroutines, channels and context. 46 topics left in the course before the final exam.

← Previous: panic and recover: only for the truly unexpected