How Go Code Is Organized

Overview

Go has strong opinions about code organization. Understanding packages, imports, and project structure is essential for writing maintainable Go programs and for your code to work correctly with the Go toolchain.

Packages: The Building Blocks

Every Go file belongs to a package. Packages group related code and control visibility.

// File: greet.go
package greeting  // Package declaration (first line)

import "fmt"

func Hello(name string) string {
    return fmt.Sprintf("Hello, %s!", name)
}

Package Rules

Rule Description
All files in a directory belong to the same package You can’t have package foo and package bar in the same folder
Package name matches directory name (by convention) Directory server/ contains package server
package main is special It defines an executable, not a library

The main Package

Only package main can produce an executable. It must have a main() function:

package main

import "fmt"

func main() {
    fmt.Println("This is the entry point")
}

Import System

Basic Imports

import "fmt"              // Standard library
import "net/http"         // Nested standard library package
import "github.com/user/repo"  // External package

Import Block (Preferred Style)

import (
    // Standard library (grouped first)
    "fmt"
    "net/http"

    // Third-party packages (blank line separator)
    "github.com/gin-gonic/gin"

    // Internal packages (blank line separator)
    "myproject/internal/config"
)

Import Aliases

import (
    "crypto/rand"
    mrand "math/rand"  // Alias to avoid collision
)

// Usage
cryptoBytes, _ := rand.Read(buf)
randomInt := mrand.Intn(100)

Blank Imports (Side Effects Only)

import (
    "database/sql"
    _ "github.com/lib/pq"  // Import for side effects (driver registration)
)

Dot Imports (Avoid in Production)

import . "fmt"

// Now you can use Println directly
Println("Hello")  // Instead of fmt.Println

Visibility: Exported vs Unexported

Go uses capitalization to control visibility:

package user

// Exported (public) - starts with uppercase
type User struct {
    Name  string  // Exported field
    Email string  // Exported field
    age   int     // unexported field (private)
}

// Exported function
func CreateUser(name string) *User {
    return &User{Name: name}
}

// unexported function (private)
func validate(u *User) bool {
    return len(u.Name) > 0
}
Name Visibility
User Exported (accessible from other packages)
Name Exported field
age Unexported (only accessible within user package)
CreateUser Exported function
validate Unexported function

Modules: Modern Dependency Management

A module is a collection of packages with a go.mod file at the root.

Creating a Module

$ mkdir myproject && cd myproject
$ go mod init github.com/username/myproject

This creates go.mod:

module github.com/username/myproject

go 1.26

Module Structure

github.com/username/myproject/
├── go.mod
├── go.sum
├── main.go
├── config/
│   └── config.go      # package config
├── internal/
│   └── database/
│       └── db.go      # package database (private)
└── pkg/
    └── api/
        └── api.go     # package api (public)

Internal Packages

The internal/ directory has special meaning: packages inside it can only be imported by code within the same module tree.

myproject/
├── internal/
│   └── secret/
│       └── secret.go   # Only myproject can import this
└── cmd/
    └── app/
        └── main.go     # Can import internal/secret
// This works (same module)
import "github.com/username/myproject/internal/secret"

// This fails (different module trying to import internal)
// Error: use of internal package not allowed

Standard Project Layout

While Go doesn’t mandate a structure, this layout is widely adopted:

myproject/
├── cmd/                    # Main applications
│   ├── myapp/
│   │   └── main.go
│   └── worker/
│       └── main.go
├── internal/               # Private packages
│   ├── config/
│   ├── database/
│   └── service/
├── pkg/                    # Public libraries (optional)
│   └── api/
├── api/                    # OpenAPI/Swagger specs
├── web/                    # Web assets
├── scripts/                # Build/install scripts
├── docs/                   # Documentation
├── go.mod
├── go.sum
└── README.md

cmd/ Directory

Each subdirectory in cmd/ becomes a separate binary:

$ go build ./cmd/myapp
$ go build ./cmd/worker

Package Naming Conventions

✅ Good ❌ Bad Why
user userPackage Avoid redundant suffixes
http httputil Only add suffix if necessary
strconv stringconversion Short but clear
io inputoutput Abbreviations OK if common

Avoid Meaningless Names

  • util → Use specific names like stringutil, timeutil
  • common → Be specific about what’s common
  • misc → Break into focused packages

Circular Import Prevention

Go prohibits circular imports. If you have:

package a → imports → package b
package b → imports → package a  // Error!

Solutions: 1. Extract common code to a third package 2. Use interfaces to break the dependency 3. Restructure your packages

Summary

Concept Purpose
Package Groups related code, controls visibility
Module Collection of packages with version info
internal/ Private packages within a module
Uppercase Exported (public)
Lowercase Unexported (private)

Exercises

  1. Create a module with two packages and have one import the other
  2. Create an internal/ package and verify external code can’t import it
  3. Create a project with cmd/ containing multiple applications