Skip to content

Basics & Syntax

Go was designed around four ideas that shape everything you write in it.

Simplicity over cleverness. The language has a small spec — just 25 keywords in total. There is deliberately no overloading, no default arguments, no implicit conversions, and no inheritance. When you read Go code written by someone else, it looks almost exactly like code you would have written yourself — that is intentional.

Fast compile. Fast compilation was a founding design goal, not an afterthought — Go was created at Google specifically to fix the slow, multi-minute C++ build times its authors were living with. Compiling even a large codebase typically takes seconds, which keeps the edit-compile-run feedback loop short and makes iteration fast.

Explicit over implicit. Type conversions and error handling are explicit — nothing happens behind the scenes that you have not written. Where a value lives, stack or heap, is decided by the compiler’s escape analysis, not by anything visible at the call site — see Pointers. This makes the code easy to trace and audit.

One obvious way. Go’s tooling enforces a single code style (gofmt), a single module system (go mod), and a single test runner (go test). Debates about formatting, import order, and project layout simply do not happen.

// The compiler rejects unused imports and unused local variables.
// This keeps every file minimal and every dependency intentional.
import "fmt"
func greet(name string) string {
return "Hello, " + name
}

go build compiles your program straight to a native machine-code binary for your GOOS/GOARCH — there is no bytecode and no virtual machine involved at runtime, unlike Java or the CPython interpreter. On this machine, go build on a one-file “hello world” program produces a self-contained executable a couple of megabytes in size (most of that is the Go runtime — the scheduler and garbage collector — linked directly into the binary), and running it needs nothing but the operating system: no Go installation, no separate runtime to ship alongside it.

go run is a convenience wrapper, not a different execution model: it compiles to a temporary binary in the background and immediately executes it, then discards the binary. It is the fastest way to try something while learning — every playground on this site behaves like go run — but for anything you deploy or benchmark repeatedly, go build once and run the resulting binary directly.

LessonTopics
Variables & Typesvar, :=, zero values, const, iota, overflow, strings as bytes
Control Flowif with init, for (three forms), labeled break/continue, switch
FunctionsMultiple returns, defer, closures, first-class functions, pass-by-value
Packages & Importspackage, import, initialization order, internal/, go.mod

By the end of this module you will be able to read and write complete idiomatic Go programs from scratch.

Every executable Go program lives in package main and execution starts in func main. Here is a program that prints a greeting and shows a few basic values:

package main
import "fmt"
func main() {
name := "Go"
version := 1.27
fast := true
fmt.Println("Hello from", name)
fmt.Println("Version:", version)
fmt.Println("Fast compile:", fast)
}

Run it below to see the output. Every concept used here — short variable declaration, basic types, fmt.Println — is covered in detail in the lessons that follow.

package main
import "fmt"
func main() {
name := "Go"
version := 1.27
fast := true
fmt.Println("Hello from", name)
fmt.Println("Version:", version)
fmt.Println("Fast compile:", fast)
// Integer arithmetic is exact; no implicit float promotion
a := 10
b := 3
fmt.Println("10 / 3 =", a/b)
fmt.Println("10 % 3 =", a%b)
}
What you getBenefitCost
Static typingThe compiler catches bugs before runtimeSlightly more code than a dynamic language
Only 25 keywordsFast to learn, easy to read other people’s codeSome features (e.g. early generics support) need workarounds
Fast compileShort feedback loop, fast iterationNo fully-featured REPL
Garbage collectionNo manual memory managementUnpredictable GC pauses in latency-critical systems
Native binary, no VMNo runtime to install, fast startup, easy to deploy (one file)Larger binary than an interpreted script; the Go runtime is always linked in
  • “Go is just a modern C.” Go has garbage collection, goroutines, interfaces, and a module system — all fundamentally different from C.
  • “Go has no OOP.” Go has structs, methods, and interfaces that support composition, but it has no class hierarchy like Java.
  • “Go is only for backend servers.” Go is also used for CLI tools, DevOps tooling, WebAssembly, embedded systems, and data pipelines.
  • “go run is how Go programs are meant to be executed in production.” go run is a development convenience that compiles to a throwaway binary every time; deployed Go programs are built once with go build and shipped as that binary.

Across every lesson in this module, the same handful of checks catch most generated-code bugs before they ship:

  • Does a := inside an if/for reuse a name (often err) that already exists in the enclosing scope, or does it silently declare a new, shadowed variable?
  • Does a break sit inside a switch (or select) that is itself inside a for? It only exits the inner construct, never the loop.
  • Does a defer sit inside a loop, or does defer f.Close() throw away an error that mattered?
  • Is a function value, or a struct field that holds one, called without a nil check first?

🐛 Spot the bug

type Config struct {
OnReady func()
}
func main() {
c := Config{}
c.OnReady()
}
Show the answer

OnReady is a function-valued field that was never assigned, so it holds its zero value, nil. Calling a nil function value panics: runtime error: invalid memory address or nil pointer dereference. This is the same class of bug as a nil pointer dereference — the fix is to check if c.OnReady != nil { c.OnReady() } before calling it, or to give Config a constructor that always sets a default no-op function.

Which of the following does Go enforce at compile time to keep code minimal?
What is the entry point of every executable Go program?
What does Go's single-style enforcement mean in practice?
What does go build produce for a simple Go program?
Does this compile?
var a int = 3
var b float64 = 1.5
fmt.Println(a + b)
What happens when this runs?
type Config struct {
    OnReady func()
}

func main() {
    c := Config{}
    c.OnReady()
}