01 / 20 · Day 1
Day 1 · Concept 01

Hello, Go

Five minutes from "never wrote Go" to "wrote your first Go". The toolchain is one binary; the language compiles in seconds; the formatter has no options to argue about. The whole point of Go is the friction is gone: install, write, run, ship.


1 · The intuition

Go was designed at Google in 2007 by Robert Griesemer, Rob Pike, and Ken Thompson. Their complaint: large C++ programs took 45 minutes to compile, and the resulting binaries needed a dozen build flags to behave. Go's answer: compile every Go program ever written in seconds; only one way to format; one binary builds, tests, and ships.

That ethos shows up everywhere. There's no separate package manager: go mod is built in. There's no formatter argument: gofmt has no options. There's no test runner: go test is the runner. You'll use five commands every day for the next year.

The five commands. go run (build & execute), go build (just build), go fmt (format), go vet (static check), go test (run tests). Memorise these; the rest you'll look up when you need them.

2 · Try it: your first Go program

Open a terminal. Make a directory. Put this in a file called main.go.

go main.go
Hit ▶ run to see the output. Click playground ↗ to take it for a spin.
// Every Go file declares which package it's part of.
// "main" is special — it's a program, not a library.
package main

// Imports go below the package line. fmt is the formatting + IO package.
import "fmt"

// "main" is the function the program runs. No arguments, no return.
func main() {
    fmt.Println("Hello, Go!")
}

Two things happened in that one command. Go compiled main.go to a temporary binary, then ran the binary. The whole process takes about 200 ms on modern hardware. The compile time is so fast Go treats it like an interpreted language, but the output is native code.

3 · Build it up: three variations

Variation 1: pass an argument

go main.go · with args
package main

import (
    "fmt"
    "os"
)

func main() {
    // os.Args is a []string. Element 0 is the program name; 1+ are the args.
    if len(os.Args) < 2 {
        fmt.Println("Usage: go run main.go <name>")
        return
    }
    fmt.Printf("Hello, %s!
", os.Args[1])
}

Variation 2: read stdin

go main.go · stdin
package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    fmt.Print("Name? ")
    scanner := bufio.NewScanner(os.Stdin)
    if scanner.Scan() {
        fmt.Printf("Hello, %s!
", scanner.Text())
    }
}

Variation 3: multiple files in one package

A real Go program rarely lives in one file. Make a second file greet.go in the same directory:

go greet.go
package main

// Capitalised names are exported from the package.
// Lower-case names are package-private. This one is package-private.
func greet(name string) string {
    return "Hello, " + name + "!"
}

And change main.go:

go main.go
package main

import "fmt"

func main() {
    fmt.Println(greet("Go"))
}

Run go run . (the dot means "all files in the current directory"). Same output. The package gathered both files automatically.

4 · The toolchain: what each command does

CommandWhat it doesWhen to use it
go run .Compile + run, throw away the binaryIterating during development
go build .Compile, leave a binary in the current dirShipping the program
go fmt ./...Reformat all .go files under the current dirBefore every commit
go vet ./...Static analysis: printf format mismatches, unreachable code, etc.Before every commit (CI usually runs it)
go test ./...Find & run all tests under the current dirWhenever you want to know if you broke something
go mod init <name>Create a new module (project)Once, when starting a project
go mod tidyAdd missing dependencies, remove unused onesAfter changing imports
go doc <pkg>Show package documentation in the terminalWhen you don't want to alt-tab to the browser

5 · Common mistakes

  • Forgetting package main. Every Go file declares its package. The one that contains func main() must be in package main. Without it, go run errors with "no main function".
  • Unused imports. Go refuses to compile if you import a package you don't use. This is a feature, keeps imports tidy, but newcomers hit it constantly. Editor with the Go extension auto-fixes on save.
  • Unused variables. Same rule: declare a variable and don't use it? Compile error. Use _ (the blank identifier) to throw away values you don't care about: _, err := os.Open("foo").
  • Capitalisation matters. A name starting with a capital letter is exported (visible outside the package). A lowercase name is private. The compiler enforces it; there is no public or private keyword.
  • Missing braces. Go requires braces around every if / for body: no exceptions. if x { doIt() } is fine; if x doIt() is a parse error.

6 · From the wild: real Go

A snippet from a project you've heard of: etcd, the distributed key-value store powering Kubernetes. This is the actual entrypoint of the etcd binary, simplified. Notice: it's the same shape you just wrote.

go etcd · cmd/etcd/main.go (simplified)
package main

import (
    "fmt"
    "os"

    "go.etcd.io/etcd/server/v3/etcdmain"
)

func main() {
    etcdmain.Main(os.Args)
    fmt.Println("etcd shutdown")
}
From the wild: github.com/etcd-io/etcd · MIT-licensed

Four imports, one main(), one library call, one print. Nothing fancy. Production Go is mostly this shape, expanded: the boilerplate stays small even at 100k-LOC scale.

7 · Coming from another language?

If you know…What's the sameWhat's different
Python Simple, readable. fmt.Println ≈ print(). Both have batteries-included stdlibs. Go is compiled: no __pycache__, no interpreter. Types are static; the compiler shouts before the program runs.
Java Static typing. Packages. A clear main() entry point. A standard build tool. No classes. No public static void main(String[]). The standard build tool (go) is one binary, not Maven + Gradle + Ant.
JavaScript / TypeScript The language feels modern. := is like const. First-class functions. Compiled and statically typed. No npm install: go mod tidy handles deps. No event loop: concurrency is goroutines.
C / C++ Pointers exist. The runtime is small. The output is a single binary. Garbage collected. No header files. No manual memory management for most code. No template metaprogramming.
Rust Static typing. Single binary. Strong standard library. No borrow checker: GC handles memory. Way faster to write small programs. Slower to run hot loops (~2× Rust on average).

8 · Exercises (~10 min)

  1. Echo three times. Modify the program to print "Hello, Go!" three times. Try a for loop: Go's only loop keyword.
  2. Greet the OS. Use runtime.GOOS (import the runtime package) to print "Hello from linux/darwin/windows". Hint: Println accepts multiple args.
  3. The 30-second build. Run go build . in your directory. What appears? Run that file directly (e.g. ./hello). Confirm: the binary doesn't need Go installed on the machine to execute.
  4. Break it on purpose. Remove the import "fmt" line. Run again. Read the compiler error. Re-import. Now remove the fmt. prefix from Println. Read that error. The error messages are part of the experience.
Why exercises matter. Reading code is not the same as writing it. A 5-minute exercise creates more muscle memory than 30 minutes of reading. The whole point of "Go in a week" is the writing.

9 · When it clicks

You can answer these questions without looking anything up:

  • What does go run . do that go build . doesn't?
  • If you have main.go and util.go in the same directory, do they share variables?
  • Why is the function called Println and not println?
  • What happens if you import fmt but never call any of its functions?

If those are all easy, you've got Day 1 of Go. Move on.

Found this useful?

Published Updated