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
.gofiles that compile together (e.g.,fmt,net/http, ormypkg). - File —
.gosource 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/projectnamego.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
.gofile starts with:
package packagename- Package names are short, lowercase, singular where possible.
package mainindicates an executable package. The compiler expectsfunc main()there.- Non-
mainpackages 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 = 8080Constants
const Pi = 3.14159Types
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 tidyPackages 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 vetin CI. - Use modules; avoid legacy GOPATH workflows.
Exercises (with brief answers)
-
Exercise: Create a module
github.com/you/calcwith a packagecalcexposingAddandMul. Then build an executable incmd/calcappthat 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/calcappcalc/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