Skip to content

Introduction to Go

Go was designed at Google by Rob Pike, Ken Thompson, and Robert Griesemer, first released in 2009. It targets the niche between systems languages (C, C++) and managed languages (Java, Python): fast Compilation, native performance, garbage collection, and built-in concurrency primitives.

Core design goals:

  1. Simplicity. The language spec is ~50 pages. There are no exceptions, no inheritance, no operator overloading, no macros. Features are additive, not combinatorial — the number of concepts you must hold in your head grows linearly with the language, not exponentially.

  2. Fast compilation. Dependency-aware compilation, minimal syntax, and no header files mean large codebases compile in seconds, not minutes.

  3. Concurrency as a first-class citizen. Goroutines are multiplexed onto OS threads by the runtime scheduler. Channels provide safe communication between goroutines without locks.

  4. Static typing with inference. Types are checked at compile time, but the compiler infers types where unambiguous. This catches bugs early without the verbosity of explicit annotations everywhere.

Go compiles to native machine code. There is no VM, no interpreter, no JIT.

Source (.go) -> go build -> linker -> native binary (statically linked by default)

The compiler pipeline:

  • Lexing and parsing -> AST
  • Type checking -> resolves all types, verifies correctness
  • SSA-based optimization -> intermediate representation for optimizations
  • Machine code generation -> targets the native architecture

The resulting binary is statically linked by default (on Linux/macOS), containing the Go runtime, Garbage collector, and all dependencies. No external shared libraries are required at runtime.

Download from go.dev/dl or use a package manager:

Terminal window
## Linux (snap)
sudo snap install go --classic
## macOS (homebrew)
brew install go
# Verify
go version

Set up workspace environment variables:

Terminal window
# GOPATH is where Go stores downloaded modules and built binaries
# Default is $HOME/go
go env GOPATH
# GOROOT is where Go is installed (in most cases auto-detected)
go env GOROOT
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}

Run directly:

Terminal window
go run main.go

Build a binary:

Terminal window
go build -o hello main.go
./hello

Every Go file belongs to a package. Executables must be in package main and expose a func main(). Library packages use any other name and are imported by path.

Compiles and executes one or more .go files in a temporary directory. Useful for development and One-off scripts. Does not produce a persistent binary.

Terminal window
go run .
go run main.go
go run cmd/server/main.go

Compiles packages and dependencies, producing a binary. By default, the binary is named after the Directory containing package main.

Terminal window
go build # binary named after current directory
go build -o myapp # custom output name
go build -ldflags "-s -w" # strip debug info, reduce binary size
go build -race # enable race detector

Cross-compilation is trivial — set GOOS and GOARCH:

Terminal window
GOOS=linux GOARCH=amd64 go build -o myapp-linux
GOOS=windows GOARCH=amd64 go build -o myapp.exe
GOOS=darwin GOARCH=arm64 go build -o myapp-mac

Builds and installs the binary to $GOPATH/bin (or $GOBIN if set). This is how CLI tools are Installed from source.

Terminal window
go install golang.org/x/tools/gopls@latest

Formats Go source code according to the standard style. There is no configuration — the format is Canonical. This eliminates style debates in code reviews.

Terminal window
go fmt ./...

Examines source code and reports suspicious constructs. Catches bugs that the compiler does not.

Terminal window
go vet ./...

Prints documentation for packages and symbols.

Terminal window
go doc fmt.Println
go doc net/http

A minimal Go project:

myproject/
go.mod
main.go
internal/
db/
db.go
cmd/
server/
main.go

The internal/ directory is special: packages inside internal cannot be imported by packages Outside the module tree rooted at the parent of internal. This enforces encapsulation.

The cmd/ directory convention holds executable entry points.

Since Go 1.16, modules are the default dependency management system. A go.mod file declares the Module path and dependency requirements:

module github.com/you/myproject
go 1.22
require (
github.com/go-chi/chi/v5 v5.0.12
github.com/lib/pq v1.10.9
)

Key module commands:

Terminal window
go mod init github.com/you/myproject # initialize
go mod tidy # sync dependencies with source
go mod download # download modules to cache
go mod verify # verify checksums
go mod graph # print dependency graph
go list -m all # list all dependencies

go.sum records the expected cryptographic checksums of every dependency. It should be committed to Version control and never edited manually.

TargetUse Case
Linux (amd64, arm64)Servers, containers, cloud
macOS (amd64, arm64)Desktop development
Windows (amd64)Desktop applications
WebAssembly (wasm)Browser, edge computing
FreeBSD/OpenBSDNetworking, infrastructure
Embedded (GOOS=linux GOARCH=arm)IoT, routers, ARM devices
  1. Not setting GOPATH/GOBIN on $PATH. go install places binaries in $GOPATH/bin or $GOBIN. If this is not on your PATH, installed tools will not be found.

  2. Using go run in production. go run compiles to a temp directory and does not produce an artifact. Use go build to produce a deployable binary.

  3. Ignoring go vet. Run go vet ./... before every commit. It catches real bugs: unreachable code, incorrect format strings, lock copies, and more.

  4. Not committing go.sum. The go.sum file is essential for reproducible builds. Always commit it alongside go.mod.

  5. Using latest in go install without pinning. go install tool@latest always fetches the newest version. Pin versions in go.mod for reproducible builds.

  6. Circular imports. Go does not allow circular imports between packages. If A imports B and B imports A, the compiler rejects it. Restructure by extracting the shared code into a third package.

Go is the language of simplicity at scale. It was designed at Google to solve the problem of large codebases with many contributors: fast compilation, clear syntax, and built-in concurrency. Goroutines are lightweight threads managed by the Go runtime, and channels are the pipes that connect them. Go intentionally omits features like inheritance, generics (until 1.18), and exceptions, favouring composition, error values, and explicit error handling. The result is code that looks similar across teams and projects.