This episode teaches you how to organize code as Go modules: go mod init, go mod tidy, and go mod verify. You will learn how to import local and external packages, manage module versions, write reusable packages, and document them with go doc.

You already mastered single-file programs in episode 3. Now it's time to organize code as a real project through modularization. The larger the application, the more important it is to separate code into packages with clear responsibilities.
Episode 4 covers the Go module system as a whole: creating a module with go mod init, importing local and external packages, managing dependency versions, writing reusable packages with correct visibility, and documenting them through go doc. By the end of this episode you will have a tidy, maintainable project structure.
A module is the distribution unit that bundles many packages together. Create your project directory, then initialize the module with a unique path. For public projects use a path like github.com/user/nama-project; for private projects you can use a simple name.
mkdir kalkulator
cd kalkulator
go mod init github.com/arman/kalkulator
cat go.modThe generated go.mod file contains the module path and the Go version. This module name determines the import path of every package inside it — choose it wisely, because it is hard to change once code is distributed.
The go mod tidy command synchronizes the go.mod and go.sum files with the actual code: it adds the dependencies that are needed, removes unused ones, and records the checksum of every module.
go mod tidy
ls go.sumgo.sum contains the cryptographic hash of every dependency. Whenever a dependency changes, the hash is updated. Don't edit go.sum manually.
Every subdirectory containing Go files is a package. The package name comes from the package declaration inside the files, not from the directory name — although by convention they match.
kalkulator/
├── go.mod
├── main.go
└── operasi/
└── operasi.goLocal packages are imported using the combination of the module path and the subdirectory name:
package operasi
func Tambah(a, b int) int {
return a + b
}package main
import (
"fmt"
"github.com/arman/kalkulator/operasi"
)
func main() {
fmt.Println(operasi.Tambah(2, 3))
}Identifiers that start with a capital letter, like Tambah, are exported and can be used from other packages. Lowercase identifiers such as jumlahInternal are only visible inside the same package. This is Go's visibility system: explicit and simple.
To add an external dependency, use go get with the path and an optional version:
go get github.com/google/uuid
go get github.com/google/uuid@v1.6.0Without a version, Go uses the newest compatible version. With @v1.6.0, the version is pinned. Afterwards, go mod tidy will synchronize the module files and add the version that is actually used.
Semantic versioning applies in Go: modules v2 and above must change their module path with a suffix like /v2. Versions below v1.0.0 are considered unstable and can change semantically. When choosing dependencies, prioritize ones that are stable and well-maintained.
Go treats comments as documentation: comments that begin with the identifier name become its official documentation. This is the convention go doc relies on.
// Package operasi menyediakan operasi aritmatika dasar.
package operasi
// Tambah menjumlahkan dua bilangan bulat.
func Tambah(a, b int) int {
return a + b
}Run go doc to view the documentation from the terminal:
go doc ./operasi
go doc operasi.TambahThe go doc operasi.Tambah command displays the documentation comment of the Tambah function along with its signature.
For public packages, add a README.md, a license file, and release versions. The module path must match the repository location so other people can import it. This consistency matters when you start distributing code, which we will cover in episode 17.
For pipelines that need deterministic builds without internet access, use go mod vendor to copy all dependencies into a vendor/ directory. Subsequent builds use -mod=vendor automatically when that directory exists.
go mod vendor
go build -mod=vendor ./...go mod verify checks whether the dependencies stored in the module cache still match the checksums in go.sum. Run this command in your pipeline before a release to make sure no files have been modified:
go mod verifySome widely used conventions in industry:
cmd directory.internal directory so they cannot be imported from outside the module.utils packages that become dumping grounds; name packages after their responsibility.We will keep using this structure from episode 7 to the end of the series, as your application grows from a calculator into an HTTP service with a database.
Episode 4 transformed you from a script writer into a Go project manager: creating modules with go mod init, synchronizing dependencies with go mod tidy, importing local and external packages, writing reusable packages with capital-letter visibility, documenting with go doc, and securing dependencies with go mod verify.
Key takeaways:
go mod init determines the import path of every package in the module.go mod tidy keeps go.mod and go.sum always in sync.go doc uses comments as official documentation./v2 suffix for majors.go mod vendor and go mod verify keep builds deterministic.In the next episode we will discuss data types, structs, interfaces, and generics — structs as data models with field tags and method receivers, idiomatic duck-typing interfaces, and modern generics with type constraints and reusable algorithms. This forms the data modeling toolkit you will use in every episode that follows.