How to Write Your First Go Program

Hello World

Write a main.go file with a fmt.Println statement and run it using the go run command to print Hello World.

The First Friction

You open your editor, create a file called main.go, and type the equivalent of print("Hello, World!"). You hit run. Instead of output, you get a compiler error about missing packages or a function that doesn't exist. Go doesn't just execute code; it demands structure. The first program isn't just about printing text. It's about learning the contract between you, the compiler, and the toolchain.

In Python or JavaScript, the interpreter often figures things out as it goes. Go is different. It compiles everything to machine code before running. The compiler checks every type, every import, and every declaration. This upfront work catches mistakes early and produces fast binaries. The friction you feel on the first run is the compiler doing its job. Once you learn the rules, the compiler becomes a reliable partner that prevents entire classes of runtime bugs.

Go code is verbose by design. The verbosity makes the structure explicit. Every dependency is visible. Every type is declared. The compiler checks them all.

Anatomy of a Go File

A Go file has three mandatory parts: a package declaration, a list of imports, and the code itself. The package declaration tells the compiler how to group this file with others. The imports list every external package the file uses. The code defines functions, variables, and types.

The package declaration must be the first line. package main is special. It tells the compiler to produce an executable binary. Any other package name produces a library that other code can import. If you name the package hello but try to run the file, the compiler rejects the program with package main: file main.go is not in package main. The toolchain expects package main for any file that contains the entry point.

Imports are explicit. Go doesn't have implicit imports. If you use a function from the fmt package, you must import fmt. This makes dependencies visible. You can look at the top of the file and see exactly what external code the file relies on. The compiler enforces this. If you import a package and don't use it, the build fails with imported and not used: "fmt". This rule prevents dependency bloat and keeps the codebase clean.

The main function is the entry point. It takes no arguments and returns nothing. The runtime invokes it automatically. If your file is in package main but lacks a main function, the linker complains with no main module or a similar error about missing entry points. The compiler needs a starting point. Without main, there is no program, only a library.

Here's the canonical first program. Every line matters.

package main // Declares this file belongs to the main package, which produces an executable.

import "fmt" // Imports the formatting package to handle text output.

// Main is the entry point. The runtime calls this function automatically.
func main() {
    // Println writes the string to standard output followed by a newline.
    fmt.Println("Hello, World!")
}

Every line in a Go file has a job. The compiler checks them all.

The Module System

Modern Go uses modules to manage dependencies. A module is a collection of Go packages stored in a single file tree. It has a go.mod file at the root that declares the module path and its dependencies. Even for Hello World, the module system is active.

If you run go run main.go in an empty directory, the tool might complain about a missing module. You initialize one with go mod init hello. This creates a go.mod file that tracks your dependencies. The module path hello is arbitrary for a simple program, but it becomes important when you import local packages or publish code.

Modules solve the dependency hell problem. They pin versions of external packages so builds are reproducible. When you add a dependency, the tool downloads it and records the version in go.mod and go.sum. This ensures everyone working on the project uses the same code.

Initialize a module early. It tracks your dependencies and versions.

Running and Building

The go command is the toolchain. It handles compilation, testing, formatting, and dependency management. For a first program, you'll use go run or go build.

go run main.go compiles the code to a temporary binary, runs it, and deletes the binary. This is fast for iteration. You change the code, run the command, and see the output immediately. The toolchain optimizes this path so you don't wait for a full build every time.

go build main.go compiles the code and produces a standalone binary named main (or main.exe on Windows). You can run this binary directly. This is what you ship to production. The binary contains everything needed to run, including the Go runtime. It doesn't require Go to be installed on the target machine.

go install compiles the code and places the binary in your GOPATH/bin directory. This is useful for tools you want to run from anywhere on your system. The tool caches the binary so subsequent installs are instant if the code hasn't changed.

go run is for iteration. go build is for shipping.

Realistic Greeting

Real programs interact with the environment. They read arguments, handle errors, and exit with codes that signal success or failure. Here's a version that greets a specific name passed as a command-line argument. It introduces the os package and shows how to check for errors.

package main // Declares this file belongs to the main package, which produces an executable.

import (
    "fmt" // Imports the formatting package for text output.
    "os"  // Imports the operating system package for arguments and exit codes.
)

// Main processes command-line arguments to greet a user by name.
func main() {
    // Args contains the command-line arguments, starting with the program name.
    args := os.Args

    // Check if a name was provided. Args[0] is always the program name.
    if len(args) < 2 {
        // Fprintln writes to stderr to indicate an error condition.
        fmt.Fprintln(os.Stderr, "Usage: greet <name>")
        // Exit with code 1 to signal failure to the shell.
        os.Exit(1)
    }

    // Println writes the greeting to standard output.
    fmt.Println("Hello,", args[1])
}

Real programs handle errors and exit codes. Don't ignore the environment.

Pitfalls and Errors

Beginners hit a few common errors when writing their first Go program. The compiler messages are precise. Read them. They tell you exactly what is wrong.

Forget the import and the compiler rejects the program with undefined: fmt. The compiler is strict about scope. Every identifier must be declared or imported. Go doesn't search global namespaces. If you use fmt.Println, you must import fmt.

Name the package anything other than main in a file you try to run, and you get package main: file main.go is not in package main. The toolchain expects package main for executables. If you want to create a library, use a different package name, but you can't run it directly.

Import a package and don't use it, and the build fails with imported and not used: "fmt". Go forbids unused imports. This keeps the dependency graph clean and prevents accidental bloat. If you remove code that uses an import, remove the import too.

The compiler errors are precise. Read them. They tell you exactly what is wrong.

Formatting and Style

Go has a strong culture around formatting. The tool gofmt formats Go code automatically. It fixes indentation, alignment, and spacing. Most editors run gofmt on save. You don't configure it. You don't argue with it. You run it.

The community accepts gofmt as mandatory. It ensures all Go code looks the same. You can read any Go file and recognize the structure immediately. This reduces cognitive load. You focus on logic, not formatting debates.

Naming conventions matter too. Public names start with a capital letter. Private names start with a lowercase letter. fmt.Println is public because Println starts with a capital. args in the example is private because it starts with a lowercase. There are no keywords like public or private. Capitalization controls visibility.

Trust gofmt. Argue logic, not formatting.

Decision Matrix

Use go run when you are experimenting and need immediate output without managing a binary file. Use go build when you need a standalone executable to ship to a server or share with a colleague. Use go install when you are building a tool you want to run from anywhere on your system. Use a module when your code imports anything outside the standard library, even if it's just a local helper package.

Pick the tool that matches your goal.

Where to go next