Until now, every program has been a single file. Real software is split across many files — sometimes hundreds — grouped into packages, and packages are grouped into modules.
Understanding the difference, and how Go finds and ties code together, makes everything from “where do I put this file?” to “how do I use a third-party library?” suddenly obvious.
A package is a folder
In Go, a package is just a folder of .go files that all declare the same package name. That’s it.
Look at any .go file in this course — the first line is always something like:
package main
That declares which package the file belongs to. Files in the same folder must use the same package name (the one exception is _test.go files, which may declare a separate <name>_test package for external tests). Files in different folders use different package names.
Two kinds of packages
There are exactly two kinds of packages in Go:
package main— produces a runnable program. Has amain()function. When yougo build, you get an executable.- Anything else — produces a library (a reusable bundle of code). Other packages can import it.
That’s it. The package name main is reserved; any other name is a library.
Splitting a project into packages
Let’s see this in action. Here’s a small project structure:
hello/
├── go.mod
├── main.go
└── greeter/
└── greeter.go
The module file:
module hello
go 1.26
The library package:
package greeter
import "fmt"
func Hello(name string) string {
return fmt.Sprintf("Hello, %s!", name)
}
func goodbye(name string) string {
return fmt.Sprintf("Goodbye, %s!", name)
}
The main program that uses it:
package main
import (
"fmt"
"hello/greeter"
)
func main() {
fmt.Println(greeter.Hello("Mani"))
}
Run from the project root:
go run main.go
Hello, Mani!
A few things to unpack:
- The
greeterfolder declarespackage greeter— its name has to match the folder. - The import path is
hello/greeter— that’s the module name (hello) plus the folder path (greeter). Go finds the package by following this path inside the module. greeter.Hello("Mani")— to use a function from another package, prefix it with the package name.
Exported vs unexported names
Look at greeter.go again:
Hello— capital first letter → exported, visible from other packagesgoodbye— lowercase first letter → unexported, only visible inside thegreeterpackage
This is one of Go’s most distinctive rules: the case of the first letter controls visibility. There’s no public or private keyword. Capital = public. Lowercase = private.
// inside main()
fmt.Println(greeter.Hello("Mani")) // ✅ works — Hello is exported
fmt.Println(greeter.goodbye("Mani")) // ❌ compile error — goodbye is not exported
This applies to everything — functions, types, methods, struct fields, constants, variables.
The standard library
Go ships with a huge standard library. Each library is a package you can import. You’ve already used several:
fmt— formatted printing and parsingstrings— string manipulation (strings.Fields,strings.Contains,strings.ToUpper, …)strconv— convert between strings and numbers (strconv.Atoi,strconv.Itoa)errors— create and inspect errors
Browse them all at pkg.go.dev/std. You’ll find packages for files, networking, JSON, HTTP, time, math, encryption, compression — most things real programs need.
Modules — Go’s projects
A module is a collection of packages that are versioned together. Every Go project is a module. Every module has a go.mod file at its root.
Earlier you ran go mod init hello. That created the module. The go.mod file looks like this:
module hello
go 1.26
module hello— the module’s name. This is the prefix for all import paths inside it.go 1.26— the Go version this module targets.
For real projects, the module name should be a URL pointing to where the code lives — for example, github.com/xqsit94/myapp. This makes it possible for other projects to depend on yours.
go mod init github.com/xqsit94/myapp
Adding a third-party dependency
Outside the standard library, the Go ecosystem has thousands of packages. To use one:
go get github.com/google/uuid
This adds github.com/google/uuid to go.mod and downloads it. Go also creates go.sum, which records the exact contents of every dependency for security.
Now you can import it like any other package:
package main
import (
"fmt"
"github.com/google/uuid"
)
func main() {
id := uuid.New()
fmt.Println("Generated ID:", id)
}
Run it:
go run main.go
Generated ID: 3f4a8c1e-9d72-4b3e-8a5f-2c9e1d4a8b6f
Useful module commands
Three commands you’ll run often:
go mod tidy # add missing imports, remove unused ones
go mod download # download all dependencies (rarely needed manually)
go list -m all # list all dependencies
go mod tidy is the most important. Run it after adding or removing imports — it cleans up go.mod and go.sum to match what your code actually uses.
The init function
Every package can define a special function called init(). It runs once, automatically, when the package is first loaded. No arguments, no return value.
package config
import "fmt"
func init() {
fmt.Println("config package initialized")
}
It’s used for things that have to happen before any other code runs — registering database drivers, validating configuration, setting up global state.
Use
initsparingly. Implicit setup makes code harder to test and reason about. When you’re not sure, prefer an explicitSetup()function that the caller invokes intentionally.
Project layout in real Go projects
Most production Go projects follow a common pattern:
myapp/
├── go.mod
├── go.sum
├── main.go # package main, the entrypoint
├── internal/ # private packages (cannot be imported by others)
│ ├── server/
│ │ └── server.go
│ └── storage/
│ └── storage.go
└── pkg/ # public packages (importable)
└── client/
└── client.go
A few conventions:
- Code under
internal/is only importable by code in the same module — Go enforces this. - Code under
pkg/is meant to be used by other projects. - The entrypoint can be a single
main.go, or undercmd/myapp/main.gofor projects with multiple binaries.
What’s next
You can now build multi-package, multi-file Go programs that handle errors gracefully. The next section covers Go’s most-loved feature: concurrency — running many things at once with goroutines and channels.