Your First Go Program and What Every Line of It Does
Write a Go hello world, run it with go run, build it into a binary, and learn why package main, the import and func main each have to be there.

Hello world in Go is five lines long, and each of them is there because the compiler will not accept the program without it. That makes it a better first lesson than it looks. If you just installed Go, you can have this running in a minute, and what you learn from taking it apart carries straight into everything else in the basics path.
Tested with Go 1.25.6, macOS on Apple Silicon (arm64)
The program
Make a folder, initialize a module in it, and create main.go:
mkdir hello && cd hello
go mod init example.test/hellopackage main
import "fmt"
func main() {
fmt.Println("Hello, Go")
}Hello, GoThat is the whole program, and the output above came from running it. The module name example.test/hello is a placeholder. For code you plan to publish it would be a repository path, such as github.com/you/hello, but for learning anything works. What go mod init does, and why a module is needed at all, is the subject of how GOPATH gave way to Go modules.
Reading it line by line
package main names the package. Every Go file starts with one. The special name main tells the toolchain this package builds into an executable. Any other name makes a library that other packages import. A folder holds exactly one package, so you cannot put package main and package hello side by side in the same directory. I will show the exact error for that further down.
import "fmt" brings in the standard library's formatting package. Import paths are strings and the last element is the name you use in the file, so fmt.Println means "the Println function from the package I imported as fmt". If you import several packages, gofmt groups and sorts them for you:
import (
"fmt"
"os"
)func main() is the entry point. It takes no arguments and returns nothing. If the program needs command line arguments you read them from os.Args, and if it needs to exit with a status you call os.Exit. The runtime calls main after all package-level variables are initialized and every init function has run, and the program ends when main returns, even if other goroutines are still running.
fmt.Println("Hello, Go") calls an exported function. In Go, a name that starts with a capital letter is visible outside its package and a lowercase name is not. That convention is the entire visibility system. There is no public keyword. It matters here because fmt.println would not compile, and the error message will tell you the function is not exported.
The tab indentation is not a preference. It is what gofmt produces, and every Go file you read will look the same.
go run versus go build
go run . compiles the package in the current directory, puts the binary in a temporary location and runs it. It is what you use while you are experimenting. go build compiles and leaves the result in the current directory, named after the module's last path element:
go build -o hello .
./helloHello, GoOn my machine that binary was 2,387,874 bytes, about 2.3 MB. That is a statically linked executable containing your code, the parts of the standard library it uses and the Go runtime, which includes the garbage collector and the scheduler. It does not depend on a Go installation, so you can copy it to another machine of the same operating system and architecture and run it. Cross compiling to a different target is a matter of setting GOOS and GOARCH before go build.
Two flags shrink it. -ldflags='-s -w' strips the symbol table and the DWARF debug information:
go build -ldflags='-s -w' -o hello-small .That took the binary to 1,606,402 bytes, a 33 percent cut. The price is that stack traces still work, but a debugger and some profiling tools get less to work with, so I keep the full binary for development and strip only the release build.
The compiler is strict on purpose
The three errors below are ones you will hit in your first hour, so it is worth seeing them once. Each is real output from the toolchain.
An import you do not use stops the build. Add import "os" to the program above without calling anything from it:
package main
import (
"fmt"
"os"
)
func main() {
fmt.Println("Hello, Go")
}# example.test/lab
./main.go:5:2: "os" imported and not usedGo treats an unused import as an error, not a warning, because dead imports used to be a major source of slow builds and misleading dependency graphs. Editors with Go support remove the line for you on save. The related error for unused local variables is covered in declared and not used.
A missing main function stops the link step. Rename main to Main and the compiler is fine with the file but the build fails:
package main
import "fmt"
func Main() {
fmt.Println("hi")
}# example.test/lab
runtime.main_main·f: function main is undeclared in the main packageThe message, function main is undeclared in the main package, is the one to remember. It does not say your capital M is wrong, so the usual cause is a typo like this one.
Two packages in one folder is the third:
go run .found packages hello (bad.go) and main (main.go) in /tmp/hhI created a second file with package hello next to main.go to get that. The fix is to put the library code in its own subdirectory, which is how larger programs are laid out anyway.
Format and check as you go
Two tools ship with Go and you should run them out of habit. gofmt -l . lists files whose formatting differs from the canonical style, and gofmt -w . rewrites them. go vet ./... finds suspicious constructs that compile fine, such as a Printf call whose format string does not match its arguments. Your editor probably runs both on save already. If it does not, set that up before you write more code. The reasoning behind a single enforced style is in why gofmt settled the formatting argument.
Running a file instead of a package
You will see go run main.go in older tutorials. It still works, and it is handy for a single-file script, but it only compiles the files you name. The moment your program grows a second file, go run main.go stops seeing it, and the build fails with errors about undefined names. go run . compiles the whole package and is the habit to build.
Where this program stops
Hello world assumes a module-aware Go and a terminal. If you are in a restricted environment where you cannot install anything, the Go Playground at go.dev/play runs the same file, though it does not offer a filesystem or network access. And if your first goal is a web service rather than a command line program, the shape of the program is the same, and the basics path builds toward it one piece at a time. For the language reference behind each of these lines, the Go specification describes program initialization and execution, and the official tour of Go is a good companion.


