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.Printlnprint(). 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 installgo 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?