Go (Golang) is a statically typed, compiled, garbage-collected programming language designed at Google for simplicity, fast compilation, and built-in concurrency via goroutines and channels — used for cloud infrastructure, web services, and CLIs at scale.
📑 Go Reference — All Topics
Structural typing, type assertions, type switches, embedding.
go keyword, channels, select, sync package.
error interface, wrapping, net/http, encoding/json.
A language built to fix Google's own build-time and concurrency problems.
One download: the compiler, stdlib, and go command in a single toolchain.
Static types, zero values, and var vs := explained.
if, for, and switch — Go has only one loop keyword.
Multiple return values, named returns, and guaranteed LIFO cleanup.
No classes: structs group data, methods bind functions to types.
The useful half of C's pointers, without the dangerous half.
Arrays are fixed; slices grow — and append() has real growth rules.
The comma-ok idiom, nil-map panics, and randomized iteration order.
Why indexing a string gives a byte, not a character.
select multiplexes channels; sync.Mutex and WaitGroup handle the rest.
Cancellation and deadlines that propagate through an entire call chain.
Type parameters, added in Go 1.18 — nine years after Go's first release.
No public/private keywords — capitalization is the whole visibility system.
No framework to install — name a function TestXxx and go test finds it.
Inspect types and values at runtime — powerful, but a last resort.
Looks like inheritance, works like composition — no dynamic dispatch.
Struct tags control JSON keys; unexported fields are silently omitted.
Two one-method interfaces — Reader and Writer — unify all Go IO.
A production HTTP server in ~9 lines, no framework required.
Capture-by-reference, and the loop-variable bug Go 1.22 finally fixed.
Go 1.23's range-over-func: custom, lazy iterators without a slice.
Escape analysis decides stack vs heap; the concurrent collector does the rest.
One binary, any platform — GOOS and GOARCH, no extra toolchain.
A production HTTP server and JSON API with zero third-party dependencies.
Worker pools, pipelines, fan-out/fan-in — all built from goroutines and channels.
Semantic versioning, go.sum integrity, and the /v2 import-path rule.
Go is the language Google built for the scale of the internet — compiled, statically typed, and fast, but with concurrency built into the language itself through goroutines and channels that make writing networked servers dramatically simpler.
What Go is
Go (also called Golang) is a statically typed, compiled, garbage-collected programming language designed at Google by Robert Griesemer, Rob Pike, and Ken Thompson, with version 1.0 released in March 2012. Go's design philosophy is radical simplicity: a small language specification, fast compilation, a powerful standard library, and first-class concurrency primitives (goroutines and channels). Go was designed to replace C++ and Java for large-scale server software at Google, where multi-million-line codebases and hundreds of engineers require fast builds and readable code.
Go is used for cloud infrastructure (Docker, Kubernetes, Terraform, Prometheus), web services, CLIs, and anywhere speed and concurrency matter. Its mascot is a gopher.
package main // every Go file belongs to a package
import (
"fmt"
"strings"
)
func main() {
// Variables: explicit or inferred
var name string = "Priya"
age := 28 // := short declaration, type inferred
const pi = 3.14159
fmt.Printf("Hello, %s! Age: %d\n", name, age)
// Slices (dynamic arrays)
fruits := []string{"mango", "apple", "banana"}
fruits = append(fruits, "guava")
fmt.Println(strings.Join(fruits, ", "))
// Maps (hash tables)
scores := map[string]int{
"Priya": 95,
"Rahul": 88,
}
scores["Anita"] = 72
fmt.Println(scores["Priya"])
}Types and interfaces
Go has structs for data and interfaces for behaviour. Go interfaces are implicit — a type satisfies an interface by implementing all its methods, without declaring that it does so. This is structural typing for interfaces: if a type has the right methods, it implements the interface. No implements keyword needed.
package main
import (
"fmt"
"math"
)
// Struct — data grouping
type Point struct {
X, Y float64
}
// Method on struct — Go's version of OOP
func (p Point) Distance() float64 {
return math.Sqrt(p.X*p.X + p.Y*p.Y)
}
// Interface — implicit implementation
type Shape interface {
Area() float64
Perimeter() float64
}
type Circle struct{ Radius float64 }
type Rect struct{ Width, Height float64 }
func (c Circle) Area() float64 { return math.Pi * c.Radius * c.Radius }
func (c Circle) Perimeter() float64 { return 2 * math.Pi * c.Radius }
func (r Rect) Area() float64 { return r.Width * r.Height }
func (r Rect) Perimeter() float64 { return 2 * (r.Width + r.Height) }
// Accepts any Shape — Circle and Rect both qualify
func printShape(s Shape) {
fmt.Printf("Area: %.2f, Perimeter: %.2f\n", s.Area(), s.Perimeter())
}
func main() {
printShape(Circle{Radius: 5})
printShape(Rect{Width: 4, Height: 3})
}Goroutines and channels
A goroutine is a lightweight, concurrently executing function. Starting one costs only a few kilobytes of stack space (versus ~1MB for an OS thread). The Go runtime multiplexes thousands of goroutines onto a pool of OS threads. Starting a goroutine: go functionName(). Goroutines communicate via channels — typed conduits that synchronise goroutines. The Go philosophy: Do not communicate by sharing memory; share memory by communicating.
package main
import (
"fmt"
"sync"
)
func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
for job := range jobs {
result := job * job // compute square
results <- result
}
}
func main() {
jobs := make(chan int, 10)
results := make(chan int, 10)
var wg sync.WaitGroup
// Start 3 worker goroutines
for w := 1; w <= 3; w++ {
wg.Add(1)
go worker(w, jobs, results, &wg)
}
// Send 9 jobs
for j := 1; j <= 9; j++ {
jobs <- j
}
close(jobs)
// Wait for all workers, then close results
go func() { wg.Wait(); close(results) }()
// Collect results
for r := range results {
fmt.Println(r)
}
}Error handling
Go has no exceptions. Functions that can fail return an error as their last return value. Callers check the error explicitly. This makes error paths visible in the code and prevents errors from being silently swallowed. The convention: return nil for success, a non-nil error for failure.
package main
import (
"errors"
"fmt"
"strconv"
)
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
func parseAndDouble(s string) (int, error) {
n, err := strconv.Atoi(s)
if err != nil {
return 0, fmt.Errorf("parseAndDouble: %w", err) // wrap error
}
return n * 2, nil
}
func main() {
result, err := divide(10, 0)
if err != nil {
fmt.Println("Error:", err) // Error: division by zero
return
}
fmt.Println(result)
// errors.Is: check error chain
_, err = parseAndDouble("not-a-number")
var numErr *strconv.NumError
if errors.As(err, &numErr) {
fmt.Println("Got a NumError:", numErr.Num)
}
}Standard library and tooling
Go's standard library is exceptionally comprehensive: net/http (full HTTP server and client), encoding/json (JSON), database/sql (database interface), crypto/tls, testing (built-in test framework), sync (mutexes, wait groups), context (cancellation propagation), io and bufio (I/O), os and filepath. Go ships with: go fmt (canonical formatter — no debates), go test, go build, go mod (modules), go vet, and go doc.
The Go scheduler: M:N threading
Go uses an M:N scheduler (M goroutines on N OS threads). The scheduler has three entities: G (goroutine), M (machine = OS thread), and P (processor = scheduling context, capped at GOMAXPROCS). Each P has a local run queue of goroutines. The scheduler uses work stealing: when a P's local queue is empty, it steals goroutines from other Ps. Goroutines are preempted at function call sites (cooperative) and since Go 1.14, asynchronously (full preemption via signals). This means a tight CPU loop is preemptible — no goroutine can starve others.
select statement: multiplexing channels
The select statement is like a switch for channels — it blocks until one of its cases can proceed, then executes that case. If multiple cases are ready simultaneously, one is chosen uniformly at random. select with a default case is non-blocking.
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() { time.Sleep(1 * time.Second); ch1 <- "one" }()
go func() { time.Sleep(2 * time.Second); ch2 <- "two" }()
// Receive from whichever channel is ready first
for i := 0; i < 2; i++ {
select {
case msg := <-ch1:
fmt.Println("Received from ch1:", msg)
case msg := <-ch2:
fmt.Println("Received from ch2:", msg)
}
}
// Timeout pattern using select + time.After
ch := make(chan int)
select {
case v := <-ch:
fmt.Println("Received:", v)
case <-time.After(500 * time.Millisecond):
fmt.Println("Timed out")
}
}Generics (Go 1.18+)
Go added generics in version 1.18 (March 2022). Type parameters use square brackets: func Map[T, U any](s []T, f func(T) U) []U. Type constraints are interfaces that specify allowed types — the built-in any constraint accepts all types; comparable accepts types that support ==. The golang.org/x/exp/slices and golang.org/x/exp/maps packages (now in standard library as of Go 1.21) use generics extensively.
package main
import "fmt"
// Generic function — T must be comparable (supports ==)
func Contains[T comparable](s []T, val T) bool {
for _, v := range s {
if v == val { return true }
}
return false
}
// Generic Map function
func Map[T, U any](s []T, f func(T) U) []U {
result := make([]U, len(s))
for i, v := range s { result[i] = f(v) }
return result
}
// Type constraint: only numeric types
type Number interface {
~int | ~int32 | ~int64 | ~float32 | ~float64
}
func Sum[T Number](s []T) T {
var total T
for _, v := range s { total += v }
return total
}
func main() {
fmt.Println(Contains([]string{"a","b","c"}, "b")) // true
fmt.Println(Map([]int{1,2,3}, func(x int) int { return x*x })) // [1 4 9]
fmt.Println(Sum([]float64{1.1, 2.2, 3.3})) // 6.6
}Context: cancellation and deadlines
The context package propagates cancellation signals and deadlines through a call chain. Every server handler receives a context.Context as its first argument. When a request is cancelled (client disconnects, timeout exceeded), the context is cancelled, and all goroutines doing work for that request can check ctx.Done() to stop early. This prevents goroutine leaks — a common source of memory issues in Go servers.
package main
import (
"context"
"fmt"
"time"
)
func doWork(ctx context.Context) error {
select {
case <-time.After(2 * time.Second):
fmt.Println("Work done")
return nil
case <-ctx.Done():
return ctx.Err() // context.DeadlineExceeded or Canceled
}
}
func main() {
// Timeout context — auto-cancels after 1 second
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
if err := doWork(ctx); err != nil {
fmt.Println("Error:", err) // Error: context deadline exceeded
}
}class keyword. You define a struct and attach methods to it using receiver functions. Embedding one struct in another provides composition (not inheritance). Go deliberately avoided inheritance.The Go memory model
The Go memory model (go.dev/ref/mem, revised 2022) defines when a goroutine is guaranteed to observe the effects of a write by another goroutine. The model is based on the happens-before relation. Key rules: (1) a send on a channel happens before the corresponding receive; (2) a receive from an unbuffered channel happens before the send on that channel completes; (3) the closing of a channel happens before a receive that returns a zero value; (4) sync.Mutex lock/unlock happens before the next lock. The 2022 revision aligned Go's model with the C11/C++11 memory model terminology, clarifying that data races produce undefined behaviour — not just unpredictable values.
Escape analysis and heap allocation
The Go compiler performs escape analysis to determine whether variables can be stack-allocated or must escape to the heap. If a local variable's address is taken and returned from the function (or stored somewhere that outlives the function), it escapes to the heap. Stack allocation is cheap (just move the stack pointer); heap allocation requires GC tracking. Use go build -gcflags="-m" to see escape analysis decisions. Understanding escape analysis helps avoid unnecessary heap allocations in hot paths.
Go 1.21+ standard library additions and toolchain
Go 1.21 (August 2023) added the slices and maps packages to the standard library (generic sort, search, equal, clone). Go 1.22 (February 2024) fixed loop variable capture in goroutines — a longstanding footgun where all goroutines in a for loop shared the same loop variable. Go 1.26 added range over functions (iterators). The Go toolchain is self-hosting — Go is compiled by Go. The reference compiler is gc (part of the Go distribution); gccgo (GCC frontend) and TinyGo (embedded/WASM) are alternative implementations.
The Go Programming Language Specification. go.dev/ref/spec. Donovan, A. A. A. & Kernighan, B. W. (2015). The Go Programming Language. Addison-Wesley. The Go Memory Model (revised 2022). go.dev/ref/mem. Go Blog: go.dev/blog/ — release notes and official posts.