Go Cheatsheet - Go Syntax & Concurrency Reference
This reference is for developers writing Go API services, concurrent workers, or ops tooling, collecting the most common syntax skeletons: variable declarations, loops and branches, functions and defer, struct methods, interfaces, and the goroutine/channel concurrency that is easiest to get wrong. Unlike bare API docs, entries give copy-ready snippets grouped by the problem each solves. After reading you should be able to write concurrency with goroutines + a WaitGroup, abstract dependencies through interfaces, and check errors Go-style with err != nil.
Typical Use Case
For engineers writing back-end services and concurrent jobs in Go. The four things you must know each have a section here: value versus reference types (reading a missing map key returns the zero value, the most common source of Go logic bugs), explicit error handling (every call returning error needs checking), implicit interface implementation (no implements keyword), and the goroutine-and-channel concurrency model. Organised by those four blocks plus structs and methods, with examples you can compile and compare directly.
Variables & Constants 7
var name string = "value"name := "value"const Pi = 3.14var (name string; age int)var arr [5]intslice := []int{1, 2, 3}m := map[string]int{"a": 1}Control Flow 8
if condition { } else { }if err := fn(); err != nil { }switch x { case 1: case 2: default: }for i := 0; i < 10; i++ { }for condition { }for { }for i, v := range slice { }for k, v := range map { }Functions 6
func add(a, b int) int { return a + b }func swap(a, b int) (int, int)func sum(nums ...int) intdefer fn()panic("error")recover()Struct & Method 5
type User struct { Name string; Age int }u := User{Name: "tom", Age: 20}func (u User) String() stringfunc (u *User) SetName(n string)u := &User{}Interface 5
type Reader interface { Read(p []byte) (int, error) }var r Reader = &File{}type Empty interface{}v, ok := i.(string)switch v := i.(type) { }Concurrency 8
go fn()ch := make(chan int)ch := make(chan int, 10)ch <- valuevalue := <-chclose(ch)select { case <-ch1: case <-ch2: }var wg sync.WaitGroupError Handling 5
if err != nil { return err }type MyError struct { msg string }func (e *MyError) Error() stringerrors.New("message")fmt.Errorf("failed: %w", err)Parameter matrix
| Parameter | Effect | Example |
|---|---|---|
:= | Short variable declaration, with the type inferred from the right side, valid inside function bodies only | name := "value" |
const | Compile-time constant, restricted to primitive types | const Pi = 3.14 |
slice := []int{} | A slice of variable length; append may swap out the underlying array | slice := []int{1, 2, 3} |
v, ok := m[k] | Tests whether a key exists in a map | v, ok := m["a"] |
defer | Runs on function exit, with the arguments evaluated at defer time | defer f.Close() |
pointer receiver | Can modify fields of the original instance and avoids copying a large struct around | func (u *User) SetName(n string) |
value receiver | Operates on a copy, so mutations leave the original instance untouched | func (u User) String() string |
v, ok := i.(string) | Checked type assertion | switch v := i.(type) {} |
interface{} | The empty interface, which can hold any type | var v interface{} |
go fn() | Starts a goroutine that the caller does not wait for | go process() |
make(chan int, n) | Buffered channel, which does not block until its capacity is reached | ch := make(chan int, 10) |
close(ch) | The sender closes, and receivers can use the close to stop ranging | close(ch) |
select | Waits on several channels at once | select { case <-done: } |
fmt.Errorf %w | Wraps an error so that errors.Is and errors.As can inspect it | fmt.Errorf("failed: %w", err) |
Common pitfalls
SymptomMap iteration order differs on every run and a test fails intermittently.
CauseGo deliberately randomises map iteration order and promises no stability.
FixCollect the keys into a slice and sort it before iterating whenever output order matters.
SymptomConcurrent code trips the race detector, which only shows up under go run -race.
CauseSeveral goroutines share one variable with no synchronisation whatsoever.
FixPass data over channels, or wait for the workers with sync.WaitGroup; always run -race before you commit.
SymptomSending on a closed channel panics.
CauseA channel may be closed only once, by its sender, and sending after the close crashes the program.
FixAgree that the sender closes; with several senders, guard the close with sync.Once.
SymptomA panic inside a goroutine kills the whole program and the caller never notices.
CauseNothing recovers inside the goroutine.
FixCall recover() from a defer inside the goroutine and turn the panic into a log line or a return value.
SymptomReading an absent map key yields 0, which then flows onward as if it were a real value.
CauseReading a missing key from a Go map returns the zero value with no error at all.
FixAlways use the two-value form v, ok := m[k] to test for presence rather than inspecting the value for zero.
SymptomA method with a value receiver mutates a field and the caller sees no change.
CauseA value receiver receives a copy, so the mutation lands on the copy and is then discarded.
FixSwitch state-changing methods to a pointer receiver (u *User), and keep the receiver kind consistent across a type.
SymptomA deferred Close reports an error or masks the real one when os.Open returned nil.
CauseOpen returns nil on failure, yet the defer still runs Close on that nil value.
FixReturn early on err != nil before registering the defer, or guard it with if f != nil { defer f.Close() }.
SymptomA defer inside a loop keeps every file open until the function returns.
CauseA defer fires when the function exits, not when the loop iteration ends.
FixExtract the per-item work into its own function and defer the Close inside that function.
Troubleshooting
1Hunt down data races
go run -race main.goThe race detector points at the exact spot where one variable is read and written; run it before every commit.
2Confirm errors are wrapped correctly
fmt.Errorf("failed: %w", err)Wrapping with %w is what lets errors.Is and errors.As follow the chain.
3Test whether a key exists in a map
v, ok := m[k]Do not treat v == 0 as absence, since the zero value may be perfectly legitimate data.
4Sort before iterating a map for stable output
for _, k := range keys { v := m[k] }Go randomises map order, so both tests and logs need explicit sorting.
5Contain panics inside a goroutine
go func() { defer func() { recover() }(); work() }()A panic in a goroutine is invisible to the caller recover and must be handled in place.
Command Examples
Error chain: wrapping, matching, and a recover backstop
func load(id string) error {
if id == "" {
return fmt.Errorf("load: %w", errors.New("empty id"))
}
return nil
}
func handle() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("panic: %v", r)
}
}()
return load("")
}The %w verb in fmt.Errorf nests the underlying error into a chain that errors.Is/As can walk layer by layer. A deferred handler needs a named return value so recover can replace the error that goes out.
Concurrent writers: WaitGroup to join, errors collected
var wg sync.WaitGroup
errCh := make(chan error, 3)
for _, id := range ids {
wg.Add(1)
go func(id string) {
defer wg.Done()
if err := save(id); err != nil {
errCh <- fmt.Errorf("save %s: %w", id, err)
}
}(id)
}
wg.Wait()
close(errCh)
for err := range errCh {
return err
}Before Go 1.22 the loop variable is shared, so closures over id all see the last value — pass it in as a parameter. errCh must be buffered: otherwise every goroutine blocks on send and wg.Wait never returns.
Closing a channel and multiplexing with select plus a timeout
go func() {
defer close(ch)
for _, v := range items {
ch <- v
}
}()
for {
select {
case v, ok := <-ch:
if !ok {
return
}
use(v)
case <-time.After(2 * time.Second):
return errors.New("consume timeout")
}
}The sender owns close and the receiver detects draining with v, ok := <-ch. When several select cases are ready Go picks one at random, so the shutdown signal has to ride the same channel as the data to stay race-free.
Struct, pointer receivers, interface satisfaction, type assertion
type User struct {
Name string
age int
}
func (u *User) SetAge(a int) { u.age = a }
type Reader interface {
Read(p []byte) (int, error)
}
var r Reader = &User{}
switch v := r.(type) {
case *User:
v.SetAge(18)
}A value receiver mutates a copy, so writes only stick through a pointer receiver (invoked on a pointer). Interface satisfaction is implicit: any type whose method set matches can be assigned to Reader.
Common Pitfalls
- Writing to a nil map panics outright (assignment to entry in nil map). var m map[string]int is read-only until you make() it or assign a literal, and the crash surfaces only when that line runs.
- defer attaches to the function, not the loop. A defer inside a for body runs when the function returns, so ten thousand iterations stack ten thousand pending calls and exhaust file handles or locks. Wrap the body in its own function when you need immediate release.
- Goroutine leaks are the hardest to trace: sending on an unbuffered channel nobody receives, or a select missing its exit branch, blocks forever with no error — just memory climbing.
- Sending on a closed channel panics, and closing twice panics too. Ownership of close must sit with exactly one sender, otherwise you get send on closed channel.
- Assigning a typed nil pointer to an interface leaves the interface non-nil, so err != nil stays true. The error reads as success and the compiler offers no hint.
- append shares the backing array: after sub := s[1:3], appending to sub can overwrite the original tail, and once capacity is exceeded it silently switches arrays — the same code then shows sporadic, hard-to-trace data corruption.
Tips
- Go's error handling is explicit: every function that may return an error must be checked with err != nil.
- goroutines are extremely lightweight (a few KB), so you can easily spawn hundreds of thousands.
- Channels are the primary way goroutines communicate, following "don't communicate by sharing memory, share memory by communicating".
FAQ
What is the difference between := and var in Go?
:= is a short variable declaration that infers the type from the right-hand side and is only valid inside functions. var requires an explicit type and can be used at package level. Prefer := in normal code because the compiler derives the type; reserve var for zero values or cases where inference is impossible. Note that := needs at least one new variable on the left, otherwise it will not compile.
Value receivers versus pointer receivers for methods?
A value receiver (func (u User) String()) operates on a copy, so field changes inside do not affect the original. A pointer receiver (func (u *User) SetName(n)) mutates the original and avoids copying large structs. Rule of thumb: use a pointer receiver when you mutate, a value receiver for pure read methods like String. Avoid mixing both on the same type, as that causes surprising interface behavior.
What is the difference between unbuffered and buffered channels?
An unbuffered channel blocks the sender until a receiver takes the value, which synchronizes the two sides by design. make(chan int, 10) creates a buffered channel where sends do not block until capacity is reached, decoupling producer and consumer pace. Use the first for strict handoff, the second when the producer outruns the consumer. Sending on a closed channel panics, while the receiver sees zero values once it is drained.
In what order do deferred functions run?
defer attaches the call to the enclosing function and runs it before returning, in last-in-first-out order — the reverse of how they were written. So the first defer registered in a block runs last, which is why you defer a close right after opening a file. If you need cleanup to happen first, register it before the others.
How should errors be handled in Go?
The idiom is to return errors rather than throw them: if err := fn(); err != nil { return err }. To preserve the original chain, wrap with fmt.Errorf("failed: %w", err) so callers can use errors.Is and errors.As to test the type or extract structured detail. Reserve panic for genuinely unrecoverable programming errors.
Official References
Each command links to its official documentation below, so you can verify the latest usage and read deeper.
Maintained by LaoHand
Publicly updated on Jul 21, 2026, continuously proofread against official docs.
Contact Us
Wrong command or description? Send us corrections, business inquiries or product feedback by email.
Contact Us