Coding · Programming languages
Go tutorial for beginners
Short answer
Go (often called Golang) is a simple, compiled, statically typed language from Google, built for fast builds, easy concurrency and reliable servers. Install Go, write a main package and run it with go run hello.go. The core ideas are a small set of types, slices and maps, structs with methods, implicit interfaces, goroutines and channels for concurrency, and errors returned as ordinary values.
What Go is and what it is used for
Go was designed at Google by Robert Griesemer, Rob Pike and Ken Thompson and released as open source in 2009. It compiles to a single static binary, starts fast, has a garbage collector and a rich standard library for networking. It is used for cloud infrastructure and command-line tools: Docker, Kubernetes, Terraform and Prometheus are written in Go, and many companies use it for web APIs and microservices. The language is deliberately small, so code from different teams looks similar.
Install Go and run Hello, world
Download Go from go.dev/dl and check it with go version. Create a folder, start a module, and add main.go:
package main
import "fmt"
func main() {
fmt.Println("Hello, world")
}go mod init example.com/hello
go run .
# Hello, world
go build # produces a standalone binary named helloExecution starts at func main in package main. Run gofmt (or go fmt) to format code; all Go code uses the same style, with tabs for indentation in real source files.
Variables and basic types
Declare variables with var name type = value, or inside functions with the short form name := value, which infers the type. Variables that are not assigned get a zero value: 0, "", false or nil.
| Type | Example | Zero value |
|---|---|---|
int, int64 | n := 42 | 0 |
float64 | x := 3.14 | 0 |
bool | ok := true | false |
string | s := "hi" | "" |
rune | r := 'é' | 0 (a Unicode code point) |
byte | b := byte(65) | 0 (alias for uint8) |
package main
import (
"fmt"
"strconv"
)
func main() {
name := "Ada"
count := 7
avg := float64(count) / 2 // explicit conversion is required
fmt.Printf("%s has %d items, avg %.1f\n", name, count, avg) // Ada has 7 items, avg 3.5
n, err := strconv.Atoi("12")
fmt.Println(n+1, err) // 13 <nil>
}Go never converts between numeric types implicitly, and the compiler rejects unused variables and unused imports.
Control flow and functions
There are no parentheses around conditions, and braces are required. for is the only loop keyword: it covers classic, while-style and range loops. Functions can return several values.
package main
import "fmt"
func divmod(a, b int) (int, int) {
return a / b, a % b
}
func main() {
score := 72
if score >= 90 {
fmt.Println("A")
} else if score >= 70 {
fmt.Println("B") // B
} else {
fmt.Println("C")
}
for i := 0; i < 3; i++ {
fmt.Print(i, " ") // 0 1 2
}
fmt.Println()
n := 10
for n > 1 { // while-style loop
n /= 2
}
q, r := divmod(17, 5)
fmt.Println(n, q, r) // 1 3 2
switch day := "SAT"; day {
case "SAT", "SUN":
fmt.Println("weekend") // no break needed
default:
fmt.Println("weekday")
}
}Slices, maps and structs
A slice is a growable view of an array and is the everyday list type. A map is a hash table. A struct groups named fields, and methods are functions with a receiver.
package main
import (
"fmt"
"sort"
)
type Point struct {
X, Y int
}
func (p Point) Add(o Point) Point { return Point{p.X + o.X, p.Y + o.Y} }
func (p *Point) Scale(k int) { p.X *= k; p.Y *= k } // pointer receiver modifies p
func main() {
nums := []int{3, 1, 2}
nums = append(nums, 5)
sort.Ints(nums)
fmt.Println(nums, len(nums), nums[1:3]) // [1 2 3 5] 4 [2 3]
ages := map[string]int{"ann": 31, "bob": 25}
ages["cy"] = 40
age, ok := ages["zed"]
fmt.Println(age, ok, len(ages)) // 0 false 3
for i, n := range nums {
fmt.Print(i, ":", n, " ") // 0:1 1:2 2:3 3:5
}
fmt.Println()
p := Point{1, 2}.Add(Point{3, 4})
p.Scale(2)
fmt.Printf("%+v\n", p) // {X:8 Y:12}
}Iteration order over a map is deliberately random; sort the keys if you need a stable order. Names that start with a capital letter (Point, Add) are exported from the package.
Interfaces
An interface lists method signatures. A type satisfies an interface simply by having those methods; there is no implements keyword.
package main
import (
"fmt"
"math"
)
type Shape interface {
Area() float64
}
type Circle struct{ R float64 }
type Rect struct{ W, H float64 }
func (c Circle) Area() float64 { return math.Pi * c.R * c.R }
func (r Rect) Area() float64 { return r.W * r.H }
func main() {
shapes := []Shape{Circle{1}, Rect{2, 3}}
total := 0.0
for _, s := range shapes {
total += s.Area()
}
fmt.Printf("%.2f\n", total) // 9.14
}Go also has generics (since Go 1.18), for example func Max[T int | float64](a, b T) T, which you will use mostly through library functions such as those in the slices and maps packages.
Signature feature: goroutines and channels
A goroutine is a lightweight thread started with the go keyword; a program can run many thousands of them. Goroutines communicate through channels, which pass values safely between them. sync.WaitGroup waits for a group of goroutines to finish.
package main
import (
"fmt"
"sync"
)
func square(n int, results chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
results <- n * n
}
func main() {
results := make(chan int, 5)
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1)
go square(i, results, &wg)
}
wg.Wait()
close(results)
sum := 0
for r := range results {
sum += r
}
fmt.Println(sum) // 55
}defer schedules a call to run when the surrounding function returns, which is how Go code closes files and unlocks mutexes. Use go run -race . to detect data races while developing.
Error handling
Go has no exceptions for ordinary failures. A function that can fail returns an error as its last result, and the caller checks it right away. Wrap errors with %w to add context while keeping the original error inspectable with errors.Is.
package main
import (
"errors"
"fmt"
"os"
)
func readConfig(path string) ([]byte, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("read config: %w", err)
}
return data, nil
}
func main() {
_, err := readConfig("missing.json")
if errors.Is(err, os.ErrNotExist) {
fmt.Println("no config file:", err)
}
}panic and recover exist, but they are reserved for programming errors and truly unexpected states, not for normal error handling.
A small complete program: a tiny web server
With only the standard library, this program serves JSON over HTTP. Run it with go run . and open http://localhost:8080/hello?name=Ann.
package main
import (
"encoding/json"
"log"
"net/http"
"time"
)
type Greeting struct {
Message string `json:"message"`
Time time.Time `json:"time"`
}
func hello(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
if name == "" {
name = "world"
}
w.Header().Set("Content-Type", "application/json")
err := json.NewEncoder(w).Encode(Greeting{"Hello, " + name, time.Now()})
if err != nil {
log.Println("encode:", err)
}
}
func main() {
http.HandleFunc("/hello", hello)
log.Println("listening on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}The text after each field type is a struct tag that sets the JSON key name. Each request is handled in its own goroutine automatically.
Common beginner mistakes
- Ignoring errors with
_. Check every returnederrorunless you are sure it cannot matter. - Expecting
appendto change the original slice in place; always use the result, as ins = append(s, x). - Starting goroutines and letting
mainreturn before they finish. Wait withsync.WaitGroupor channels. - Using a value receiver when the method must modify the struct; use a pointer receiver such as
func (p *Point).
Where to go next
Take the interactive Tour of Go, then read Effective Go and the standard library documentation at pkg.go.dev. Learn go test for unit tests and benchmarks, the context package for timeouts and cancellation, and modules for dependencies.
FAQ
Is it called Go or Golang?
The official name is Go. "Golang" comes from the old website address golang.org and is still used as a search term.
Is Go object-oriented?
Partly. Go has methods and interfaces but no classes or inheritance; you build types by embedding structs and composing small interfaces.
Is Go faster than Python?
For most CPU-bound work, yes, because Go compiles to native machine code and has static types. Python can still be fast when the heavy work happens inside libraries written in C.
What is the difference between a goroutine and a thread?
A goroutine is managed by the Go runtime and starts with a small stack that grows as needed. The runtime schedules many goroutines onto a few operating system threads, so goroutines are much cheaper to create.