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 a main() function. When you go 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:

  1. The greeter folder declares package greeter — its name has to match the folder.
  2. 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.
  3. 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 packages
  • goodbye — lowercase first letter → unexported, only visible inside the greeter package

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 parsing
  • strings — 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 init sparingly. Implicit setup makes code harder to test and reason about. When you’re not sure, prefer an explicit Setup() 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 under cmd/myapp/main.go for 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.

Toggle theme (T)