Program structure (Golang)
21 Sep 2025
Program structure (Golang)
Overview
A Go program is organized into modules, packages, and files. A module is the top-level unit (declared by go.mod
) and contains one or more packages. A package groups related source files. A package built as package main
with a func main()
is an executable. All Go source files in the same directory must declare the same package name (with one exception: tests may use <pkg>_test
).
Key terms:
- Module — logical project set, declared with
go.mod
. - Package — collection of
.go
files that compile together (e.g.,fmt
,net/http
, ormypkg
). - File —
.go
source file; must begin withpackage <name>
.
Module (go.mod)
Create a module for your project. This file pins module path and dependency versions.
Example:
# initialize a module
go mod init github.com/you/projectname
go.mod
example:
module github.com/you/projectname
go 1.21
require (
github.com/some/dependency v1.2.3
)
go
version and module path are used by the toolchain and package import paths.
Package and file layout
- Every
.go
file starts with:
package packagename
- Package names are short, lowercase, singular where possible.
package main
indicates an executable package. The compiler expectsfunc main()
there.- Non-
main
packages are libraries (importable by other packages).
Example file hello.go
:
package main
import "fmt"
func main() {
fmt.Println("Hello, world!")
}
If you have multiple files in the same directory, they must use the same package:
/cmd/myapp/main.go -> package main
/internal/util/util.go -> package util
/internal/util/parse.go -> package util
Imports
Use imports to reference other packages.
import (
"fmt"
"net/http"
"github.com/you/projectname/internal/util"
)
- Standard library imports go first (you can group them).
- Use fully qualified module paths for external packages.
- The Go tool automatically fetches required modules (managed in
go.mod
).
The main
function and init
func main()
is program entrypoint inpackage main
.- You can define multiple
init()
functions across files in the same package — they run beforemain()
in unspecified order.
Example:
package main
import "fmt"
func init() {
fmt.Println("initializing...")
}
func main() {
fmt.Println("main started")
}
Order: all package-level init()
run (for dependencies first), then main()
.
Declarations: variables, constants, types, functions
Variables
var x int = 3
var y = 4 // type inferred
z := 5 // short declaration (only inside functions)
Package-level vars:
package config
var Port = 8080
Constants
const Pi = 3.14159
Types
Define types to create clear APIs:
type User struct {
ID int
Name string
}
type Handler func(http.ResponseWriter, *http.Request)
Functions
func Add(a, b int) int {
return a + b
}
Exported vs unexported: Identifiers starting with an uppercase letter are exported (visible to importers). Lowercase is package-private.
Error handling
Idiomatic Go returns errors as values.
func ReadFile(path string) ([]byte, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
return data, nil
}
data, err := ReadFile("foo.txt")
if err != nil {
log.Fatalf("read failed: %v", err)
}
Do not use exceptions. Prefer explicit error returns.
Formatting, style, and tools
gofmt
/go fmt
— automatic style formatter. Always run it; CI should enforce it.go vet
— static analysis for suspicious code.golangci-lint
— optional linter aggregator for more checks.- Keep functions small and focused; prefer composition over heavy inheritance.
Build & run
- Run a single file or package:
go run ./cmd/myapp
- Build binary:
go build -o bin/myapp ./cmd/myapp
- Test:
go test ./...
- Tidy dependencies:
go mod tidy
Packages vs directories: recommended layout
A typical simple layout:
/cmd/
myapp/
main.go # package main
/internal/
app/
app.go # package app
util/
util.go
/pkg/
shared/
shared.go # optional: libraries you may publish
go.mod
go.sum
/cmd/<appname>
allows multiple executables. /internal
packages cannot be imported outside the module (enforced by Go).
Example: Hello app with a small package
File: go.mod
module github.com/you/helloapp
go 1.21
File: cmd/hello/main.go
package main
import (
"github.com/you/helloapp/greet"
)
func main() {
greet.SayHello("Brian")
}
File: greet/greet.go
package greet
import "fmt"
// SayHello prints a greeting.
func SayHello(name string) {
fmt.Printf("Hello, %s!\n", name)
}
Run:
go run ./cmd/hello
# Output: Hello, Brian!
Multiple source files in same package
greet
could be split:
greet/messages.go
package greet
func message(name string) string {
return "Hello, " + name + "!"
}
greet/greet.go
package greet
import "fmt"
func SayHello(name string) {
fmt.Println(message(name))
}
Both files compile together because they are in the same directory and package.
Visibility and API design
- Export only what's necessary.
- Keep types and helper functions unexported if they are internal details.
- Use small interfaces near their use-site (interface segregation).
Example:
type Saver interface {
Save(data []byte) error
}
Initialization patterns
Prefer explicit initialization where possible. Use init()
sparingly — it makes program start behavior implicit.
Better:
func NewApp(cfg Config) (*App, error) {
// construct and return
}
Then in main
:
func main() {
cfg := loadConfig()
app, err := NewApp(cfg)
if err != nil { log.Fatal(err) }
app.Run()
}
Concurrency (brief note)
Concurrency is a core language feature with goroutines and channels. Organize concurrent code in packages that provide safe abstractions (e.g., worker pools, context cancellation).
Example:
go func() {
// background goroutine
}()
Use context.Context
to propagate cancellation.
Common conventions & best practices
- Keep one package per directory.
- Keep files small and cohesive.
- Prefer composition over embedding heavy hierarchies.
- Document exported identifiers with comments that start with the identifier name:
// Add returns the sum of a and b.
func Add(a, b int) int { ... }
- Use
go fmt
&go vet
in CI. - Use modules; avoid legacy GOPATH workflows.
Exercises (with brief answers)
-
Exercise: Create a module
github.com/you/calc
with a packagecalc
exposingAdd
andMul
. Then build an executable incmd/calcapp
that uses it.Answer (structure):
go mod init github.com/you/calc /calc/calc.go -> package calc (func Add, Mul) /cmd/calcapp/main.go -> package main (imports github.com/you/calc) go run ./cmd/calcapp
calc/calc.go
:package calc func Add(a, b int) int { return a + b } func Mul(a, b int) int { return a * b }
cmd/calcapp/main.go
:package main import ( "fmt" "github.com/you/calc" ) func main() { fmt.Println(calc.Add(2,3)) fmt.Println(calc.Mul(4,5)) }
-
Exercise: Show how to create and use an
init()
function and explain order of execution.Answer:
init()
runs beforemain()
. If package A imports B, B’sinit()
runs before A’sinit()
. -
Exercise: Convert a small multi-file package into a single-file package and explain visibility changes.
Answer: Combining files into one file doesn’t change exported/unexported rules; uppercase still exports.
Extended example: small CLI that reads a file and prints word count
go.mod
module github.com/you/wc
go 1.21
cmd/wc/main.go
package main
import (
"flag"
"fmt"
"log"
"github.com/you/wc/wc"
)
func main() {
path := flag.String("file", "", "file to count words in")
flag.Parse()
if *path == "" {
log.Fatal("please supply -file")
}
count, err := wc.CountWords(*path)
if err != nil {
log.Fatalf("error: %v", err)
}
fmt.Printf("Words: %d\n", count)
}
wc/wc.go
package wc
import (
"bufio"
"os"
)
func CountWords(path string) (int, error) {
f, err := os.Open(path)
if err != nil {
return 0, err
}
defer f.Close()
scanner := bufio.NewScanner(f)
scanner.Split(bufio.ScanWords)
count := 0
for scanner.Scan() {
count++
}
if err := scanner.Err(); err != nil {
return 0, err
}
return count, nil
}
Build & run:
go run ./cmd/wc -file sample.txt