A quick-reference for the most commonly used Go features. Go is a statically typed, compiled language designed for simplicity, concurrency, and fast compile times. Goroutines and channels make concurrent programming idiomatic, and the standard library covers most everyday needs.
Go is statically typed β every variable has a type known at compile time. The short declaration := infers the type; var is required at package level and when zero-initialisation matters.
x := 42
only inside functions; type inferred; cannot redeclare in same scope
var x int = 42
zero-valued if no initializer: 0, "", false, nil; required at package level
val, err := strconv.Atoi("42")
idiomatic way to return both result and error
_, err := os.Open("file")
discards a value; Go requires every declared variable to be used
const (
A = iota // 0
B // 1
C // 2
)
iota resets to 0 in each const block; useful for enums
Go built-in functions are pre-declared in the universe block and available without imports.
len(slice) / cap(slice)
len(m) / len(str)
len returns count of items/bytes; cap returns allocated capacity
s := make([]int, len, cap)
m := make(map[string]int)
p := new(int) // returns *int pointer to zero value
s = append(s, 1, 2)
n := copy(dst, src)
copy returns number of elements copied (min len of dst and src)
delete(m, "key")
clear(slice) // zero-fills slice
clear(m) // empties map
Go has a single loop keyword for, covering C-style, while-style (for condition), and infinite loops (for {}). defer runs when the enclosing function returns, in LIFO order.
for i := 0; i < n; i++ {} // C-style
for condition {} // while-style
for {} // infinite loop
for i, v := range slice {} // index + value
for k, v := range m {} // map key + value
for _, v := range slice {} // discard index
range over a string yields runes, not bytes
if err := doWork(); err != nil {
err is scoped to the if/else block β keeps error handling local
switch x {
case 1, 2:
fmt.Println("one or two")
default:
fmt.Println("other")
}
no implicit fallthrough; use the fallthrough keyword explicitly if needed
defer f.Close()
arguments are evaluated immediately; body runs at function exit in LIFO order
In Go, strings are immutable read-only byte slices ([]byte), typically UTF-8 encoded. Converting to []rune allows character-wise inspection.
s := "Hello, δΈη"
sub := s[0:5] // "Hello" (byte slicing)
runes := []rune(s) // unicode code points
char := runes[7] // 'δΈ'
len(s) is byte count (13); len([]rune(s)) is character count (9)
strings.Contains(s, "sub")
strings.HasPrefix(s, "pre")
strings.HasSuffix(s, "suf")
strings.Index(s, "x") // -1 if not found
strings.ToLower(s) / strings.ToUpper(s)
strings.TrimSpace(s) / strings.Trim(s, " ")
parts := strings.Split("a,b,c", ",")
joined := strings.Join(parts, ", ")
var b strings.Builder
b.WriteString("hello ")
b.WriteString("world")
res := b.String() // efficient concatenation
formatted := fmt.Sprintf("%s = %d", "x", 42)
Sequence β []T (slice) or [N]T (fixed array), Map β map[K]V.
Slices are the primary sequence type β a view over an underlying array. Maps are hash tables with O(1) average lookup. Both are reference types.
s := []int{1, 2, 3}
length and capacity both 3; backed by a new array
s := make([]int, length, capacity)
pre-allocates backing array; avoids repeated copies on append
s = append(s, 4, 5)
s = append(s, other...)
must reassign; append may return new slice if capacity exceeded
s[low:high]
shares the backing array; mutations affect the original; high is exclusive
m := map[string]int{"a": 1}
m := make(map[string]int)
a nil map panics on write; always initialise with make or a literal
v, ok := m["key"]
ok is false if key is absent; v is the zero value β always check ok
delete(m, "key")
safe to call even if the key does not exist
| Interaction | []T (slice) |
[N]T (array) |
map[K]V |
string |
struct |
|---|---|---|---|---|---|
| Access item | s[i] | arr[i] | m[k] / v, ok := m[k] | s[i] (byte) | st.Field |
| Access last item | s[len(s)-1] | arr[len(arr)-1] | Unordered | s[len(s)-1] | N/A (named fields) |
| Add / Append | s = append(s, x) | N/A (fixed size) | m[key] = val | s += "x" | N/A (fixed fields) |
| Insert at index | append(s[:i], append([]T{x}, s[i:]...)...) | N/A | N/A (by key) | s[:i] + "x" + s[i:] | N/A |
| Update item | s[i] = val | arr[i] = val | m[k] = val | Immutable (strings.Replace) |
st.Field = val |
| Update last item | s[len(s)-1] = val | arr[len(arr)-1] = val | Unordered | s[:len(s)-1] + "c" | N/A |
| Remove item | append(s[:i], s[i+1:]...) | N/A | delete(m, k) | strings.Replace(s, sub, "", 1) | N/A |
| Pop (remove & get) | x, s = s[len(s)-1], s[:len(s)-1] | N/A | v := m[k]; delete(m, k) | c, s = s[len(s)-1], s[:len(s)-1] | N/A |
| Check existence | slices.Contains(s, x) (O(n)) | slices.Contains(arr, x) (O(n)) | _, ok := m[k] (O(1)) | strings.Contains(s, sub) | Compile time check |
| Clear / Empty | s = s[:0] / s = nil | arr = [N]T{} | clear(m) / m = make(...) | s = "" | st = Struct{} |
| Mutable in-place? | Yes | Yes | Yes | No (Immutable) | Yes |
| Ordered? | Yes (0-indexed) | Yes (0-indexed) | No (Unordered) | Yes (0-indexed) | N/A (Named fields) |
Functions are first-class values. Multiple return values replace exceptions for expected errors. Closures capture variables by reference.
func divide(a, b float64) (result float64, err error) {
naked return returns named values; useful for short functions
func sum(nums ...int) int {
sum(1, 2, 3)
sum(nums...)
nums is a slice inside the function; spread with ...
fn := func(x int) int { return x * 2 }
function types are comparable; useful for callbacks and strategy pattern
adder := func(x int) func(int) int {
return func(y int) int { return x + y }
}
add5 := adder(5)
add5(3) // 8
x is captured by reference β mutations in the closure affect it
Go uses composition over inheritance. Interfaces are satisfied implicitly β any type implementing the required methods qualifies. Use pointer receivers when the method needs to mutate the struct.
type Point struct {
X, Y float64
}
p := Point{X: 1.0, Y: 2.0}
p.X = 3.0
unset fields are zero-valued; use field names in literals for clarity
func (p Point) Dist() float64 {}
func (p *Point) Scale(f float64) {}
value receiver = copy; pointer receiver = mutates original; be consistent on a type
type Stringer interface {
String() string
}
// any type with String() string satisfies this
// no "implements" keyword needed
s, ok := val.(string)
ok is false if assertion fails; without ok it panics on mismatch
switch v := i.(type) {
case string:
fmt.Println("string:", v)
case int:
fmt.Println("int:", v)
default:
fmt.Printf("unknown: %T\n", v)
}
In Go, errors are values β functions return them as a second return value and callers check immediately. Use %w to wrap errors so callers can inspect the cause with errors.Is and errors.As.
result, err := doSomething()
if err != nil {
return fmt.Errorf("context: %w", err)
}
%w wraps the original error; %v includes it in the message but doesn't wrap
errors.Is(err, os.ErrNotExist)
errors.As(err, &target)
both unwrap the error chain; Is checks identity, As extracts a typed value
type NotFoundError struct{ Name string }
func (e *NotFoundError) Error() string {
return e.Name + " not found"
}
implement the error interface by defining Error() string
Goroutines are lightweight threads managed by the Go runtime β run thousands concurrently. Channels let goroutines communicate safely. "Do not communicate by sharing memory; share memory by communicating."
go func() { ... }()
starts a new goroutine; the calling goroutine continues immediately
ch := make(chan int)
ch <- 42 // send
v := <-ch // receive
unbuffered: send blocks until receiver is ready
ch := make(chan int, 10)
send blocks only when buffer is full; useful for decoupling producers and consumers
select {
case v := <-ch1:
fmt.Println("received", v)
case ch2 <- x:
fmt.Println("sent")
default:
fmt.Println("no channel ready")
}
picks a ready case at random if multiple are ready; default makes it non-blocking
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
doWork()
}()
wg.Wait()
Add before the goroutine starts; Done via defer to handle panics
var mu sync.Mutex
mu.Lock()
defer mu.Unlock()
// critical section
protect shared state; defer unlock ensures release even on panic
Idiomatic Go patterns that appear in almost every real-world codebase.
if err != nil {
return fmt.Errorf("open config: %w", err)
}
// caller can inspect
errors.Is(err, os.ErrNotExist)
var e *PathError
errors.As(err, &e)
%w wraps for errors.Is/As; %v just includes the message without wrapping
type Option func(*Server)
func WithTimeout(d time.Duration) Option {
return func(s *Server) { s.timeout = d }
}
s := NewServer(WithTimeout(5 * time.Second))
cleaner than a config struct when options are few and optional
var (
instance *DB
once sync.Once
)
func GetDB() *DB {
once.Do(func() { instance = connect() })
return instance
}
safe singleton; Do executes the function exactly once even under concurrency
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
result, err := db.QueryContext(ctx, query)
always defer cancel() to avoid context leaks; propagate ctx through call chain
func TestAdd(t *testing.T) {
tests := []struct{ a, b, want int }{
{1, 2, 3}, {0, 0, 0}, {-1, 1, 0},
}
for _, tt := range tests {
if got := Add(tt.a, tt.b); got != tt.want {
t.Errorf("Add(%d,%d) = %d; want %d", tt.a, tt.b, got, tt.want)
}
}
}
idiomatic Go test style; add t.Parallel() inside the loop for parallel subtests
The Go standard library covers HTTP, JSON, file I/O, string manipulation, cryptography β most everyday tasks need no external packages.
fmt.Sprintf("%s=%d", k, v)
fmt.Fprintf(os.Stderr, "err: %v\n", err)
%v = default, %+v = with field names, %#v = Go syntax, %T = type
strings.Contains / HasPrefix / HasSuffix
strings.Split / Join / TrimSpace / ToLower
strings.Builder // efficient concatenation
strings.Builder avoids O(nΒ²) cost of repeated + concatenation
strconv.Itoa(42)
strconv.Atoi("42")
strconv.FormatFloat(f, 'f', 2, 64)
Atoi returns (int, error); ParseInt/ParseFloat give more control
os.Args[1:]
os.Exit(1)
val := os.Getenv("HOME")
os.Exit skips deferred functions β prefer returning an error when possible
log.Fatalf("open %s: %v", path, err)
Fatal calls os.Exit(1) after logging; use log/slog for structured logging