Summary
Go is a statically typed, compiled programming language designed for simplicity, efficiency, and excellent concurrency support. Created by Google, Go excels in backend development with features like goroutines, channels, fast compilation, and a rich standard library. This cheatsheet covers essential Go concepts including syntax, concurrency patterns, HTTP servers, database operations, testing, and best practices for building scalable backend applications.
Basic Syntax & Types
Variable Declaration
var x int = 10
var y = 20 // Type inference
z := 30 // Short declaration
const PI = 3.14 // Constants
Basic Types
// Numeric
int, int8, int16, int32, int64
uint, uint8, uint16, uint32, uint64
float32, float64
complex64, complex128
// Other
bool
string
byte (alias for uint8)
rune (alias for int32, represents Unicode)
Zero Values
var i int // 0
var f float64 // 0.0
var b bool // false
var s string // ""
var p *int // nil
Arrays & Slices
// Array (fixed size)
var arr [5]int
arr := [3]int{1, 2, 3}
// Slice (dynamic)
var slice []int
slice := make([]int, 5, 10) // len=5, cap=10
slice = append(slice, 6)
// Slice operations
slice[1:3] // Sub-slice
len(slice) // Length
cap(slice) // Capacity
Maps
// Declaration
m := make(map[string]int)
m := map[string]int{"a": 1, "b": 2}
// Operations
m["key"] = 10
value := m["key"]
value, exists := m["key"]
delete(m, "key")
// Iteration
for k, v := range m {
fmt.Println(k, v)
}
Control Structures
If/Else
if x > 0 {
// code
} else if x < 0 {
// code
} else {
// code
}
// With initialization
if val := compute(); val > 0 {
// use val
}
Switch
switch x {
case 1:
// code
case 2, 3:
// code
default:
// code
}
// Type switch
switch v := i.(type) {
case string:
// v is string
case int:
// v is int
}
Loops
// For loop
for i := 0; i < 10; i++ {
// code
}
// While-like
for x < 10 {
// code
}
// Infinite
for {
// code
}
// Range
for i, v := range slice {
// i: index, v: value
}
Functions
Basic Function
func add(x, y int) int {
return x + y
}
// Multiple returns
func swap(x, y string) (string, string) {
return y, x
}
// Named returns
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return // naked return
}
Variadic Functions
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
Function as Values
func compute(fn func(int, int) int) int {
return fn(3, 4)
}
// Anonymous function
result := func(x, y int) int {
return x + y
}(5, 3)
Defer
func example() {
defer fmt.Println("world")
fmt.Println("hello")
// Output: hello world
}
// Multiple defers (LIFO)
defer fmt.Println("1")
defer fmt.Println("2")
// Output: 2 1
Structs & Methods
Struct Definition
type Person struct {
Name string
Age int
}
// Initialization
p1 := Person{"Alice", 30}
p2 := Person{Name: "Bob", Age: 25}
p3 := &Person{Name: "Charlie"} // Pointer
Methods
// Value receiver
func (p Person) String() string {
return fmt.Sprintf("%s (%d)", p.Name, p.Age)
}
// Pointer receiver (can modify)
func (p *Person) SetAge(age int) {
p.Age = age
}
Embedding
type Employee struct {
Person // Embedded struct
Position string
}
e := Employee{
Person: Person{"Alice", 30},
Position: "Developer",
}
Interfaces
Interface Definition
type Writer interface {
Write([]byte) (int, error)
}
type Reader interface {
Read([]byte) (int, error)
}
// Interface composition
type ReadWriter interface {
Reader
Writer
}
Implementation
type MyWriter struct{}
func (m MyWriter) Write(data []byte) (int, error) {
// Implementation
return len(data), nil
}
Empty Interface
var i interface{} = "hello"
// Type assertion
s, ok := i.(string)
if ok {
fmt.Println(s)
}
Common Interfaces
// Stringer
type Stringer interface {
String() string
}
// Error
type error interface {
Error() string
}
Error Handling
Basic Error Handling
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
// Usage
result, err := divide(10, 0)
if err != nil {
log.Fatal(err)
}
Custom Errors
type MyError struct {
Code int
Message string
}
func (e *MyError) Error() string {
return fmt.Sprintf("Error %d: %s", e.Code, e.Message)
}
Error Wrapping
import "fmt"
err := fmt.Errorf("failed to process: %w", originalErr)
// Check wrapped error
if errors.Is(err, ErrNotFound) {
// Handle specific error
}
Concurrency
Goroutines
go func() {
fmt.Println("Running concurrently")
}()
// With parameters
go processData(data)
WaitGroup
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
fmt.Printf("Worker %d\n", id)
}(i)
}
wg.Wait()
Mutex
type Counter struct {
mu sync.Mutex
value int
}
func (c *Counter) Increment() {
c.mu.Lock()
defer c.mu.Unlock()
c.value++
}
func (c *Counter) Value() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.value
}
Channels
Basic Channels
// Unbuffered channel
ch := make(chan int)
// Buffered channel
ch := make(chan int, 10)
// Send and receive
ch <- 42 // Send
value := <-ch // Receive
// Close channel
close(ch)
Channel Direction
// Send-only
func send(ch chan<- int) {
ch <- 42
}
// Receive-only
func receive(ch <-chan int) {
value := <-ch
}
Select Statement
select {
case msg1 := <-ch1:
fmt.Println("Received:", msg1)
case msg2 := <-ch2:
fmt.Println("Received:", msg2)
case <-time.After(1 * time.Second):
fmt.Println("Timeout")
default:
fmt.Println("No messages")
}
Channel Patterns
// Fan-in
func fanIn(ch1, ch2 <-chan int) <-chan int {
out := make(chan int)
go func() {
for {
select {
case v := <-ch1:
out <- v
case v := <-ch2:
out <- v
}
}
}()
return out
}
// Worker pool
func workerPool(jobs <-chan int, results chan<- int) {
for job := range jobs {
results <- job * 2
}
}
Testing
Basic Testing
// test file: math_test.go
func TestAdd(t *testing.T) {
result := add(2, 3)
if result != 5 {
t.Errorf("Expected 5, got %d", result)
}
}
// Table-driven tests
func TestAddTable(t *testing.T) {
tests := []struct {
a, b, want int
}{
{1, 2, 3},
{0, 0, 0},
{-1, 1, 0},
}
for _, tt := range tests {
got := add(tt.a, tt.b)
if got != tt.want {
t.Errorf("add(%d, %d) = %d; want %d",
tt.a, tt.b, got, tt.want)
}
}
}
Benchmarking
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
add(2, 3)
}
}
Mocking
type Database interface {
Get(key string) (string, error)
}
type MockDB struct {
data map[string]string
}
func (m *MockDB) Get(key string) (string, error) {
if val, ok := m.data[key]; ok {
return val, nil
}
return "", errors.New("not found")
}
HTTP & Web
Basic HTTP Server
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:])
}
func main() {
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
HTTP Client
resp, err := http.Get("https://api.example.com/data")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
JSON Handling
type User struct {
Name string `json:"name"`
Email string `json:"email"`
}
// Encoding
user := User{"Alice", "alice@example.com"}
jsonData, err := json.Marshal(user)
// Decoding
var user User
err := json.Unmarshal(jsonData, &user)
// From HTTP request
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&user)
Middleware
// Middleware type
type Middleware func(http.HandlerFunc) http.HandlerFunc
// Logging middleware
func loggingMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
log.Printf("Started %s %s", r.Method, r.URL.Path)
next(w, r)
log.Printf("Completed in %v", time.Since(start))
}
}
// Auth middleware
func authMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token == "" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
next(w, r)
}
}
// Chain middlewares
func chainMiddleware(h http.HandlerFunc, middlewares ...Middleware) http.HandlerFunc {
for i := len(middlewares) - 1; i >= 0; i-- {
h = middlewares[i](h)
}
return h
}
// Usage
http.HandleFunc("/api/protected",
chainMiddleware(handler, loggingMiddleware, authMiddleware))
Database Operations
SQL Database
import "database/sql"
import _ "github.com/lib/pq" // PostgreSQL driver
db, err := sql.Open("postgres", "connection_string")
defer db.Close()
// Query
rows, err := db.Query("SELECT id, name FROM users WHERE age > $1", 18)
defer rows.Close()
for rows.Next() {
var id int
var name string
err := rows.Scan(&id, &name)
}
// Single row
var name string
err := db.QueryRow("SELECT name FROM users WHERE id = $1", 1).Scan(&name)
// Execute
result, err := db.Exec("INSERT INTO users(name, age) VALUES($1, $2)", "Alice", 30)
Prepared Statements
stmt, err := db.Prepare("INSERT INTO users(name, age) VALUES($1, $2)")
defer stmt.Close()
result, err := stmt.Exec("Bob", 25)
Transactions
tx, err := db.Begin()
if err != nil {
return err
}
_, err = tx.Exec("UPDATE accounts SET balance = balance - $1 WHERE id = $2", 100, 1)
if err != nil {
tx.Rollback()
return err
}
_, err = tx.Exec("UPDATE accounts SET balance = balance + $1 WHERE id = $2", 100, 2)
if err != nil {
tx.Rollback()
return err
}
return tx.Commit()
Context Package
Basic Context Usage
ctx := context.Background()
// With timeout
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
// With cancel
ctx, cancel := context.WithCancel(ctx)
defer cancel()
// With value
ctx = context.WithValue(ctx, "userID", 123)
Context in HTTP
func handler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
select {
case <-time.After(5 * time.Second):
fmt.Fprintf(w, "Hello!")
case <-ctx.Done():
http.Error(w, "Request cancelled", http.StatusRequestTimeout)
}
}
Context Propagation
func fetchData(ctx context.Context, url string) error {
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return err
}
resp, err := http.DefaultClient.Do(req)
// Handle response
return nil
}
Common Patterns
Singleton
var (
instance *Database
once sync.Once
)
func GetDatabase() *Database {
once.Do(func() {
instance = &Database{}
instance.Connect()
})
return instance
}
Builder Pattern
type ServerConfig struct {
host string
port int
timeout time.Duration
}
type ServerBuilder struct {
config ServerConfig
}
func (b *ServerBuilder) Host(h string) *ServerBuilder {
b.config.host = h
return b
}
func (b *ServerBuilder) Port(p int) *ServerBuilder {
b.config.port = p
return b
}
func (b *ServerBuilder) Build() *Server {
return &Server{config: b.config}
}
Options Pattern
type Option func(*Server)
func WithTimeout(t time.Duration) Option {
return func(s *Server) {
s.timeout = t
}
}
func WithMaxConn(n int) Option {
return func(s *Server) {
s.maxConn = n
}
}
func NewServer(opts ...Option) *Server {
s := &Server{
timeout: 30 * time.Second,
maxConn: 100,
}
for _, opt := range opts {
opt(s)
}
return s
}
Performance & Best Practices
String Building
// Inefficient
s := ""
for i := 0; i < 1000; i++ {
s += "a"
}
// Efficient
var sb strings.Builder
for i := 0; i < 1000; i++ {
sb.WriteString("a")
}
s := sb.String()
Slice Pre-allocation
// Inefficient
var slice []int
for i := 0; i < 1000; i++ {
slice = append(slice, i)
}
// Efficient
slice := make([]int, 0, 1000)
for i := 0; i < 1000; i++ {
slice = append(slice, i)
}
Interface Checks
// Compile-time interface check
var _ io.Writer = (*MyWriter)(nil)
Resource Management
// Always defer cleanup
file, err := os.Open("file.txt")
if err != nil {
return err
}
defer file.Close()
// Use defer for mutex unlock
mu.Lock()
defer mu.Unlock()
Error Best Practices
// Define sentinel errors
var (
ErrNotFound = errors.New("not found")
ErrInvalid = errors.New("invalid input")
)
// Wrap errors with context
if err != nil {
return fmt.Errorf("failed to process user %d: %w", userID, err)
}
Concurrency Best Practices
// Don't communicate by sharing memory; share memory by communicating
// Use channels for coordination
// Limit goroutines
sem := make(chan struct{}, 10) // Max 10 concurrent
for _, item := range items {
sem <- struct{}{}
go func(item Item) {
defer func() { <-sem }()
process(item)
}(item)
}
Key Concepts & Comparisons
Goroutines vs OS Threads
| Aspect | Goroutines | OS Threads |
|---|---|---|
| Memory | ~2KB initial stack | ~2MB fixed stack |
| Creation Cost | Very low | High |
| Management | Go runtime | OS kernel |
| Scheduling | Cooperative | Preemptive |
| Context Switch | Fast | Slow |
Concurrency Primitives
| Tool | Use Case | Example |
|---|---|---|
| Channels | Communication between goroutines | ch := make(chan int) |
| Mutex | Protecting shared state | sync.Mutex |
| WaitGroup | Waiting for goroutines to finish | sync.WaitGroup |
| Context | Cancellation and timeouts | context.WithTimeout() |
Method Receivers
| Type | Syntax | Use When |
|---|---|---|
| Value Receiver | func (t Type) method() |
No modification needed, small structs |
| Pointer Receiver | func (t *Type) method() |
Need to modify, large structs, consistency |
// Value receiver - cannot modify original
func (p Person) GetName() string {
return p.Name
}
// Pointer receiver - can modify original
func (p *Person) SetName(name string) {
p.Name = name
}
Interface Implementation
| Concept | Description | Example |
|---|---|---|
| Implicit | No explicit "implements" | Any type with matching methods |
| Duck Typing | "If it walks like a duck..." | Automatic satisfaction |
| Empty Interface | interface{} accepts any type |
func Print(v interface{}) |
Memory Allocation
| Function | Purpose | Usage |
|---|---|---|
make() |
Initialize reference types | make([]int, 5), make(chan int) |
new() |
Allocate memory for any type | new(int) returns *int |
| Literal | Direct initialization | []int{1,2,3}, Person{Name: "John"} |
Error Handling Philosophy
// Go's explicit error handling
result, err := operation()
if err != nil {
return fmt.Errorf("operation failed: %w", err)
}
// Panic/Recover for exceptional cases only
func riskyOperation() {
defer func() {
if r := recover(); r != nil {
log.Printf("Recovered from panic: %v", r)
}
}()
// risky code here
}
Go Concurrency Patterns
Worker Pool Pattern
func workerPool(numWorkers int, jobs <-chan Job, results chan<- Result) {
var wg sync.WaitGroup
for i := 0; i < numWorkers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for job := range jobs {
results <- processJob(job)
}
}()
}
go func() {
wg.Wait()
close(results)
}()
}
Pipeline Pattern
func pipeline(input <-chan int) <-chan int {
output := make(chan int)
go func() {
defer close(output)
for n := range input {
output <- n * 2 // Transform data
}
}()
return output
}
Fan-Out/Fan-In Pattern
func fanOut(input <-chan Work, workers int) []<-chan Result {
outputs := make([]<-chan Result, workers)
for i := 0; i < workers; i++ {
output := make(chan Result)
outputs[i] = output
go func() {
defer close(output)
for work := range input {
output <- process(work)
}
}()
}
return outputs
}
func fanIn(inputs ...<-chan Result) <-chan Result {
output := make(chan Result)
var wg sync.WaitGroup
wg.Add(len(inputs))
for _, input := range inputs {
go func(ch <-chan Result) {
defer wg.Done()
for result := range ch {
output <- result
}
}(input)
}
go func() {
wg.Wait()
close(output)
}()
return output
}
Quick Reference & Best Practices
Essential Built-in Functions
| Function | Purpose | Example |
|---|---|---|
make() |
Create reference types | make([]int, 5, 10) |
new() |
Allocate zeroed memory | new(int) returns *int |
len() |
Get length | len(slice), len(map) |
cap() |
Get capacity | cap(slice), cap(channel) |
append() |
Add to slice | append(slice, element) |
copy() |
Copy slice elements | copy(dest, src) |
delete() |
Remove map entry | delete(map, key) |
close() |
Close channel | close(ch) |
Core Standard Library Packages
| Package | Purpose | Key Functions |
|---|---|---|
fmt |
Formatted I/O | Printf, Sprintf, Scanf |
net/http |
HTTP client/server | ListenAndServe, Get, Post |
encoding/json |
JSON handling | Marshal, Unmarshal |
database/sql |
Database interface | Open, Query, Exec |
context |
Request context | WithTimeout, WithCancel |
sync |
Synchronization | Mutex, WaitGroup, Once |
time |
Time operations | Now, Sleep, Parse |
io |
I/O primitives | Copy, ReadAll, Writer |
Performance Best Practices
✅ String Building: Use strings.Builder instead of string concatenation
✅ Slice Allocation: Pre-allocate slices with known capacity
✅ Interface Checks: Use compile-time interface verification
✅ Resource Management: Always use defer for cleanup
✅ Error Handling: Wrap errors with context using fmt.Errorf
✅ Concurrency: Use channels for communication, mutexes for shared state
Common Pitfalls to Avoid
| Pitfall | Problem | Solution |
|---|---|---|
| Goroutine leaks | Goroutines not properly terminated | Use context for cancellation |
| Range loop variable | Capturing loop variable in closure | Create local copy |
| Slice capacity | Frequent reallocations | Pre-allocate with make([]T, 0, capacity) |
| Interface comparison | nil interface vs nil pointer |
Check both value and type |
| Defer in loops | Defers accumulate | Use function wrapper |
Production Deployment Checklist
- Build Optimization: Use
go build -ldflags="-s -w"for smaller binaries - Graceful Shutdown: Handle OS signals properly
- Health Checks: Implement
/healthendpoints - Logging: Use structured logging (JSON format)
- Metrics: Implement Prometheus metrics
- Profiling: Enable pprof endpoints for debugging
- Environment Config: Use environment variables for configuration
- Container Security: Use minimal base images (scratch, alpine)
Memory Management Tips
// Prevent goroutine leaks
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
go func() {
select {
case <-ctx.Done():
return // Goroutine exits cleanly
case result := <-heavyComputation():
// Process result
}
}()
// Efficient slice operations
// Clear slice while keeping capacity
slice = slice[:0]
// Release memory completely
slice = nil