Notes · Learn Go › LESSON 1 OF 8

Hello, Go

Your first Go program, and the four pieces you'll see in every .go file you ever read. By the end you'll know why Go compiles instead of interprets, and you'll have run real code three different ways.

Contents
  1. The classic "Hello, World"
  2. Anatomy of every Go file
  3. Python ↔ Go side-by-side
  4. How Go runs your code
  5. What's inside the binary
  6. Three ways to run a Go program
  7. Real-world examples
  8. Python-dev gotchas
  9. Glossary

The classic "Hello, World"

Every programming book starts here. Yours included. Save this as hello.go:

hello.go
package main

import "fmt"

func main() {
    fmt.Println("Hello, Go!")
}

Then in your terminal:

terminal
$ go run hello.go
Hello, Go!

▶ Open Go Playground   (hover any code block above and click Copy, then paste)

The 5-year-old version
Imagine you want a robot to say "Hello, Go!" out loud. The robot only understands very specific instructions.

package main = "Hey robot, this is a complete instruction sheet, not a recipe card stuck inside another book."
import "fmt" = "I'm going to need the talking tool from the toolbox."
func main() = "Start here. This is where you begin."
fmt.Println("Hello, Go!") = "Use the talking tool to say the words inside the quotes."

Every Go program needs all four pieces. They are the magic words that wake the robot up.

Anatomy of every Go file

Look at that 5-line program again. Three pieces appear in every Go file you'll ever see:

hello.go package main import "fmt" func main () { fmt. Println ( "Hello, Go!" ) } 1. Package declaration "main" = a runnable program 2. Imports stdlib pkgs you'll use 3. func main() where execution starts 4. Your actual code do something useful
Same four pieces in every Go file. Memorize this shape - it never changes.
PieceWhat it doesPython analog
package main Declares the file's package. main means "this is an executable." Library code uses other names. The if __name__ == "__main__": dance, but mandatory and at the top.
import "fmt" Pulls in the formatting/printing package from the standard library. import sys or from os import path.
func main() The entry point. Go starts here. Returns nothing, takes no arguments. The body inside if __name__ == "__main__":.
fmt.Println(...) Calls Println from the fmt package. Prints + newline. print("...").
Why the { } braces? Go uses C-style braces instead of Python's indentation. The compiler does not care about whitespace. (But gofmt, the auto-formatter, does. Always run it.)

Python ↔ Go side-by-side

The same program in both languages. Read both, then read them again:

Python  ·  hello.py
def main():
    print("Hello, Go!")

if __name__ == "__main__":
    main()
Go  ·  hello.go
package main

import "fmt"

func main() {
    fmt.Println("Hello, Go!")
}

The mental shifts

DifferenceWhy Go is like this
Go requires package + import declarations always.Go has no "script mode." Every file belongs to a package. This makes large codebases unambiguous - you always know what scope something lives in.
Braces { } instead of indentation.The compiler parses braces faster and more reliably than significant whitespace. Indentation is enforced by gofmt anyway.
No if __name__ == "__main__": guard.Only files in package main have a main() function. Library packages can't accidentally execute. Cleaner separation.
Println capitalized.In Go, an uppercase first letter means "exported / public." Lowercase means "private to the package." It's the access-control system. (More in Lesson 8.)

How Go runs your code (vs Python)

This is the biggest mental shift coming from Python. Python is interpreted. Go is compiled. Watch what that means:

Python (interpreted) hello.py source code python3 (CPython) interprets line-by-line Output "Hello, Go!" ⚠ Needs Python installed ⚠ Re-interpreted each run Go (compiled) hello.go source code go build (compiler) translates to machine code ./hello native binary Output "Hello, Go!" ✓ Binary runs on its own ✓ Fast every run after
Python rereads and re-interprets your source every time. Go does the translation work once, then runs the result.
The 5-year-old version
Imagine you write a story in English.

Python is like asking a translator to read your story out loud in French. Every time you want to hear it again, the translator has to read and translate, line by line.

Go is like printing the French version once, into a book. After that, anyone can read the French book - no translator needed. It's faster and they don't even need to know English.

That book? In Go we call it a "binary." It's a single file that contains everything the computer needs to run your program.
PythonGo
Edit-run cycleEdit, run. (Fast loop, but slow execution.)Edit, compile, run. (Tiny compile, then fast execution.)
Errors caughtAt runtime, when the buggy line executes.At compile time, before the program ever runs.
DeployingShip code + Python + venv + deps.Ship one binary.
Runtime errorsType errors crash mid-run.Most type errors can't compile, so they can't happen.
Bonus: cross-compile for free. On your Mac, run GOOS=linux GOARCH=amd64 go build hello.go and you get a Linux binary. No Docker, no VM. This is why Kubernetes is written in Go - one codebase, binaries for every OS.

What's inside that binary?

"One static binary" sounds like magic. It isn't - it's just that Go packs everything your program needs into that one file. Here's what's inside:

SOURCES hello.go fmt (stdlib) strconv (stdlib) runtime (Go's) os, syscall, ... go build compile + link ./hello (one file, ~8 MB) YOUR CODE ~2 KB STANDARD LIBRARY (only what you used) fmt, strconv, reflect, ... ~2 MB GO RUNTIME garbage collector, goroutine scheduler, ... ~4 MB DATA SECTION string constants, type info, symbol table, ... ~2 MB
A Go binary is "fat" because it includes the runtime + every stdlib package you used. That's why it can run on a bare server with no Go installed.
SectionWhat it doesPython equivalent?
Your codeThe machine instructions for main() and any functions you wrote.Your .py files (but compiled to native, not bytecode).
Standard libraryCompiled code of every stdlib package you imported. Unused packages are stripped.The CPython implementation of fmt, os, etc.
Go runtimeThe garbage collector, scheduler, memory allocator. Always included.The CPython interpreter itself.
Data sectionStrings, type metadata, reflection info. Read-only at runtime.Embedded constants in CPython bytecode.
Why the binary is "8 MB" for a Hello World: Because it ships with the entire Go runtime + a chunk of stdlib. The same hello world is bigger than CPython's bytecode - but smaller than CPython itself + your venv. Net win.

Three ways to run a Go program

You'll use all three. Pick the right tool for the moment:

(a) go run

When: hacking on a single file, learning, quick experiments.

$ go run hello.go
Hello, Go!

Compiles + runs in one step, throws the binary away. Closest to python hello.py.

(b) go build

When: shipping to production, packaging a real tool.

$ go build hello.go
$ ls
hello   hello.go
$ ./hello
Hello, Go!

Produces a real executable. Copy this file to any machine of the same OS/arch and run it.

(c) Go Playground

When: sharing a snippet, no Go installed, trying things from your phone.

# Open in browser:
go.dev/play

# Paste, hit Run.
# Runs server-side.

Zero install. Great for examples and sharing - all the links in this course go here.

Trade-off triangle go run fast loop go build production Playground share online Speed: ★★☆ Ship-able: ✗ Install: needs Go Use for: dev loop Speed: ★★★ Ship-able: ✓ Install: needs Go Use for: prod binary Speed: ★☆☆ Ship-able: ✗ Install: just a browser Use for: examples
Pick the tool that matches what you're doing. Most of this course you'll use (a) and (c).
Pro tip: Run gofmt -w hello.go after editing. It reformats your file to the official Go style automatically. Most editors (VS Code with Go extension) do this on save. Stop wasting brain cycles on whitespace.

The same skeleton in real Go programs

Hello, World is cute. But that exact 4-line skeleton (package main + import + func main) is the start of every Go program ever shipped. Three real ones, each under 15 lines:

BackendHTTP API

A web server that responds to any request with "Hello, Go!" - the entire program:

server.go
package main

import (
    "fmt"
    "net/http"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintln(w, "Hello, Go!")
    })
    http.ListenAndServe(":8080", nil)
}

Run it, then visit http://localhost:8080. That's a production-grade HTTP server in 12 lines. No Flask, no uvicorn. Just Go's stdlib.

CLICommand-line tool

A tiny CLI that greets whoever you pass via --name:

greet.go
package main

import (
    "flag"
    "fmt"
)

func main() {
    name := flag.String("name", "world", "who to greet")
    flag.Parse()
    fmt.Printf("Hello, %s!\n", *name)
}
terminal
$ go build -o greet
$ ./greet --name=Sunil
Hello, Sunil!

No argparse, no click. The flag stdlib package handles it. Real Go CLIs (kubectl, terraform, docker) start exactly this way.

k8sKubernetes / Cloud

A skeleton for a Kubernetes pod-counter - the same shape, just more imports:

podcount.go
package main

import (
    "context"
    "fmt"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/client-go/kubernetes"
    "k8s.io/client-go/tools/clientcmd"
)

func main() {
    cfg, _ := clientcmd.BuildConfigFromFlags("", "~/.kube/config")
    cs, _ := kubernetes.NewForConfig(cfg)
    pods, _ := cs.CoreV1().Pods("").List(context.TODO(), metav1.ListOptions{})
    fmt.Printf("Hello, %d pods!\n", len(pods.Items))
}

Same 4-piece skeleton. The whole Kubernetes ecosystem - kubectl, operators, controllers, even k8s itself - is built on this shape.

Notice: Every program above is package main + import + func main(). That's the only entry-point shape Go has. You've already learned it.

Python-dev gotchas (read twice)

1. Unused imports are a compile error.

If you write import "fmt" and don't use it, the program won't build. Coming from Python where unused imports are a lint warning at worst, this feels harsh. It keeps codebases clean. (VS Code's Go extension auto-removes them on save.)

2. Unused variables are also a compile error.

Declare x := 5 and never use x? Build fails. Use _ (underscore) if you want to throw away a value: _, err := f().

3. The opening { must be on the same line.

This is valid: func main() {
This is not: func main() then { on its own line.
Go auto-inserts semicolons at end-of-line, so a brace on a new line becomes a syntax error.

4. fmt.Println, not print.

There is a builtin print() in Go, but never use it. It's for early debugging of the runtime itself, undocumented, and goes to stderr in weird ways. Always use fmt.Println.

5. File names are snake_case.go, not CamelCase.go.

Convention: lowercase with underscores. http_server.go, not HTTPServer.go. Inside the file, types and functions are CamelCase - just not file names.

Glossary

TermMeaning
package mainMarks a file as part of an executable program (vs a library). Required for any file with a main() function.
func main()The entry point of an executable. Takes no args, returns nothing. Exactly one per program.
importBrings in another package by import path. Single: import "fmt". Grouped: import ( "fmt"; "os" ).
fmtThe standard formatting/printing package. Has Println, Printf, Sprintf, etc.
go runCompile and run in one step, then discard the binary. For development.
go buildCompile to a real executable. For shipping.
gofmtAuto-formatter. Run gofmt -w yourfile.go or let your editor do it on save.
BinaryThe compiled executable file. Self-contained: no runtime needed on the target machine.
Go runtimeThe scheduler + GC + memory allocator that ships inside every Go binary.
Compile-time errorAn error caught by go build before the program runs. Fix and rebuild.
Standard library (stdlib)Packages that ship with Go: fmt, os, net/http, encoding/json, etc.

Origins, gotchas, and a quick check

Backstory and pitfalls that make this chapter stick.

What's in a name?
Go
Language designed at Google by Rob Pike, Ken Thompson, Robert Griesemer (2007-2009), first public release Nov 2009. Name is intentionally short; the team's internal joke is 'gopher' for the mascot. Often called 'Golang' for search-engine reasons.
fmt
Short for 'format'. Verb-based formatting inspired by C's printf. %v (default value), %d (decimal), %s (string), %+v (with field names).
main
The entry-point package. Every executable Go program has package main + func main(). Libraries use other package names.
gofmt
The canonical formatter. The Go team's stance: there's no style debate because gofmt is the style. Run automatically by most editors on save.
go
The toolchain command. go run, go build, go test, go mod, go vet. Same name as the language.
gopher
The unofficial mascot, designed by Renee French (Rob Pike's partner). The cute blue-grey creature on Go conference t-shirts everywhere.
Fun fact Go was conceived during a slow C++ build at Google in 2007. Rob Pike's famous quote: "It's been a long time since I'd had a Hello, World program take 45 minutes to compile." The team's goal was a language that compiled in seconds, not minutes. Go 1.0 (March 2012) introduced a backwards-compatibility promise that's still honored: code written for 1.0 should still compile on 1.22+ today. Very few mainstream languages can claim that.
Bug hunt

This program compiles. What happens when you run it?

main.go
package main

import "fmt"

func main() {
    var s []string
    fmt.Println(len(s))
    s[0] = "hello"
    fmt.Println(s[0])
}
Click to reveal the bug

The first Println prints 0 - nil slices have length 0 and that's totally valid. The crash is on s[0] = "hello" - you can't index into a nil slice. Panic: runtime error: index out of range [0] with length 0.

Fix: s = append(s, "hello") works because append handles nil slices. Or initialize: s := make([]string, 1).

Real-world incident The nil-interface trap (Cloudflare, ~2013) 6-hour data lag

A cron task processing access logs at Cloudflare had this innocent-looking pattern:

cron.go
var err error
err = (*MyError)(nil)
if err != nil {
    handleError(err)   // called with nil internals
}

The comparison err != nil was true, because the error interface had a type assigned even though the underlying pointer was nil. handleError then called methods on the nil pointer, panicking the binary. The cron daemon died silently and logs piled up for 6 hours.

Lesson: Returning a typed-nil from a function whose return type is the error interface does NOT compare equal to nil. Always return raw nil for the error path, never a typed pointer cast to error. Tools like staticcheck SA4023 catch this.

Quick check

Test yourself

Don't peek. Think first, then click to reveal each answer.

1. What's the difference between go run and go build?

  • go run compiles + executes; go build only compiles to a binary
  • go run is interpreted; go build is compiled
  • go run is for dev; go build is for tests
  • No difference - aliases
Show answer
Answer: a. go run compiles to a temp directory and immediately executes - convenient for one-shot scripts. go build emits a binary you can ship. Both go through the same compiler.

2. Every Go executable needs which two things?

  • package main and func main()
  • package main and func init()
  • import and func main()
  • package and func start()
Show answer
Answer: a. package main declares this is an entry-point package; func main() is what the runtime calls. Both are required. func init() is optional (runs before main) - some programs have it, most don't.

3. What does gofmt do?

  • Lints for bugs
  • Re-formats your code to canonical Go style
  • Generates documentation from comments
  • Strips comments from your source
Show answer
Answer: b. gofmt rewrites the file to the Go team's canonical style (tabs for indent, brace placement, gofmt-aligned struct field tags). Run automatically by most editors. Stops style bikeshedding before it starts.

4. The fmt.Println function comes from which standard-library package?

  • format
  • fmt
  • print
  • io
Show answer
Answer: b. fmt for "format". Imported via import "fmt". The package also has Printf, Sprintf, Errorf, Fprintln, and friends.

5. A Go source file that starts with package mypkg (not main) is...

  • Still an executable program
  • A library file - cannot be run with go run, must be imported
  • A syntax error
  • A test file
Show answer
Answer: b. Only package main makes an executable. Other packages are libraries, imported by main or other packages. go run on a non-main file will fail with "no Go files in package".