Hello, World & Your First Module
The "Hello, World!" program is a rite of passage. It's trivial, but
writing one proves your toolchain works and gives us a tiny anchor to
talk about Go's structure, the go CLI, and modules.
Learning objectives
- Write, run, and build your first Go program.
- Explain every line of a minimal Go source file.
- Initialize a Go module with
go mod init. - Choose correctly between
go run,go build, andgo install. - Format code with
go fmtand cross-compile to other platforms.
Writing Hello World
Create a directory for your first program and put a single Go file in it:
mkdir -p ~/code/hello
cd ~/code/hello
touch main.go
Open main.go in your editor and type:
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
Seven lines of code. We'll unpack each one.
Line by line
package main
Every Go source file begins with a package declaration.
The package main is special: it's the one Go recognizes as
a runnable program. Any other package name (e.g.
mathutil) would produce a library, not a binary.
We'll revisit packages in depth in Chapter 18.
import "fmt"
import pulls in another package so you can use its
exported names. fmt is Go's standard library package for
formatted I/O: printing, string formatting, scanning input.
You'll see fmt in almost every program.
goimports enabled) manages imports automatically
, add or remove fmt.Println calls and the import line
will update on save.
func main() { … }
The entry point. Exactly one main
function must live in the main package. When Go launches
your binary, main() runs and, when it returns, the program
exits.
fmt.Println("Hello, World!")
fmt.Println prints its arguments to standard output,
separated by spaces, followed by a newline. The string
"Hello, World!" is a Go string literal (Chapter 7
is dedicated to strings).
What's absent
Notice what's not there:
- No semicolons ending lines. Go inserts them automatically.
- No explicit types on anything. Go can tell
"Hello, World!"is a string. - No exception handling syntax. Errors in Go are values, returned from functions (Chapter 23).
Running it with go run
The quickest way to see output:
go run main.go
You should see:
Hello, World!
go run compiles your code to a temporary binary, executes
it, then deletes the binary. It's perfect for iteration, no artifacts
left behind.
You can also pass a directory or a package path:
go run . # compile + run the current package
go run ./cmd/app # compile + run a package at a relative path
Once your program has more than one file, you'll use go run .
rather than listing individual files.
Your first module
Go projects are organized into modules. A module is
just a directory with a go.mod file in it. Even for a
one-file hello-world, it's good practice to initialize one:
go mod init example.com/hello
This creates a file called go.mod:
module example.com/hello
go 1.22
The module line declares the module's import path. It's
typically a URL where the code lives (like github.com/you/hello)
, even if you never publish it. The go line records the
minimum Go version this module supports. You rarely edit
go.mod by hand; Go tools do it when you add dependencies.
go.mod unlocks import path resolution (you can split
your program into multiple packages), dependency tracking, and
reproducible builds. It's free; just do it. Chapter 18 dives deeper.
Building a binary with go build
To produce an actual executable file:
go build .
Go compiles your package and places the binary in the current directory.
On macOS/Linux it's called hello; on Windows
hello.exe. Run it:
./hello # macOS / Linux
hello.exe # Windows
You can also name the output file:
go build -o greet .
./greet
This binary is static: it has no external runtime dependencies. Copy it to a machine with the same OS/architecture and it runs. That's the Go deployment superpower in a nutshell.
go run vs build vs install
| Command | What it does | Use when |
|---|---|---|
go run |
Compiles to a temp binary, runs it, deletes it | Iterating, changing code and re-running quickly |
go build |
Produces a binary in the current directory (or wherever -o says) |
Shipping a standalone executable |
go install |
Compiles and places the binary in $GOPATH/bin (usually ~/go/bin) |
Installing a CLI tool system-wide, e.g. installing someone else's tool |
Typical example of go install:
# Install golangci-lint from the internet, now available on your $PATH
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
This is how you install most Go CLI tools. Make sure ~/go/bin
is on your PATH (you set this up in Chapter 3).
Formatting with go fmt
Go has one canonical code style, enforced by the
gofmt tool. No tabs-vs-spaces debates, no bikeshedding
over where to put braces. Your editor should format on save (Chapter 3)
, but you can also run it manually:
go fmt ./...
(The ./... pattern means "the current directory and all
subdirectories recursively", a common Go idiom.)
An even better version is goimports, which runs
gofmt and manages the import block
for you:
go install golang.org/x/tools/cmd/goimports@latest
goimports -w .
gofmt
It's actually freeing to not think about formatting. Every Go project
you'll ever see is formatted the same way. You can skim strangers'
code without friction. This is one of the small things that makes Go
a pleasure to write over time.
Exit codes
Every program that finishes reports an exit code to its parent (usually
the shell). 0 means success; anything non-zero means
something went wrong. Go's main function returns
0 implicitly. To exit with a specific code:
package main
import (
"fmt"
"os"
)
func main() {
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "usage: greet NAME")
os.Exit(1) // exit code 1, "usage error"
}
fmt.Println("Hello,", os.Args[1])
}
Two new ideas:
os.Argsis a slice containing the program name followed by command-line arguments. (We'll cover slices in Chapter 13.)fmt.Fprintln(os.Stderr, ...)writes to standard error instead of standard output, the Unix-conventional channel for errors.
Check the exit code after running:
go run . # prints usage, exits 1
echo $? # prints: 1
go run . Alice # prints "Hello, Alice"
echo $? # prints: 0
Cross-compilation
Go can build binaries for other operating systems and CPU architectures
from your current machine. Two environment variables control this:
GOOS and GOARCH.
# From a Mac, build a Linux binary
GOOS=linux GOARCH=amd64 go build -o hello-linux .
# ...a Windows binary
GOOS=windows GOARCH=amd64 go build -o hello.exe .
# ...a Raspberry Pi (ARM) binary
GOOS=linux GOARCH=arm64 go build -o hello-pi .
See the full list with:
go tool dist list
This is why Go has become the go-to language for shipping CLI tools and cloud-native infrastructure: one developer, on one laptop, can produce binaries for every platform the tool needs to run on, no Docker or cross-toolchain setup required.
Check your understanding
Practice exercises
Make it yours
Modify the Hello World program so that:
- If given a command-line argument, greet that person by name.
- If given two arguments, greet both:
"Hello, Alice and Bob!" - If given zero arguments, greet the world.
Run it with go run . Alice, then
go run . Alice Bob, then go run ..
Show a working solution
package main
import (
"fmt"
"os"
"strings"
)
func main() {
names := os.Args[1:]
switch len(names) {
case 0:
fmt.Println("Hello, World!")
case 1:
fmt.Printf("Hello, %s!\n", names[0])
default:
fmt.Printf("Hello, %s!\n", strings.Join(names, " and "))
}
}
You'll see strings.Join, switch, and
slicing again in future chapters. Don't worry if the syntax feels
new, you're pattern-matching, which is exactly the right skill.
Ship a real binary
Build your greeter as an actual binary, then run it without Go.
go build -o greet .
./greet Alice
Bonus: cross-compile for a different OS and verify you get a file with the right extension:
GOOS=linux GOARCH=amd64 go build -o greet-linux .
file greet-linux # should say: ELF 64-bit LSB executable
What you've just done
You produced a self-contained binary with no runtime dependencies
, the same kind of artifact Docker, Kubernetes, and Terraform
ship. The file command confirms the binary matches
the target OS.
This pattern (cross-compile to a target, upload, run) is the core of Go's deployment story.
Further reading
First program shipped, well done.