| Type | Zero Value |
int, float64 | 0, 0.0 |
bool | false |
string | "" |
pointer, func, interface, slice, channel, map | nil |
struct | All fields are zero values |
array | All elements are zero values |
var x int = 10 // Package or function level
var y = 10 // Type inferred
z := 10 // Short declaration, function level only
// := can declare multiple variables
a, b := 1, "hello"
// := requires at least one new variable on left
x, c := 2, "world" // x is reassigned, c is new
// Arrays: fixed size, value type
arr := [5]int{1, 2, 3, 4, 5}
arr2 := arr // Copy!
// Slices: dynamic, reference type
s := []int{1, 2, 3}
s2 := s // Shared underlying array!
// Slice internals
type slice struct {
array unsafe.Pointer // Pointer to underlying array
len int // Current length
cap int // Capacity
}
s := make([]int, 2, 4) // len=2, cap=4
s = append(s, 1, 2) // len=4, cap=4 (no reallocation)
s = append(s, 3) // len=5, cap=8 (reallocated, doubled)
// If multiple slices share the same backing array,
// append may or may not modify the original
func example() {
fmt.Println("1") // Executes first
defer fmt.Println("2") // Executes last (LIFO)
defer fmt.Println("3") // Executes before "2"
fmt.Println("4") // Executes second
}
// Output: 1, 4, 3, 2
// Arguments are evaluated immediately
x := 1
defer fmt.Println(x) // Prints 1, not 2
x = 2
| Aspect | Goroutine | Thread |
| Stack | ~2KB (dynamic) | 1-8MB (fixed) |
| Creation | ~0.3μs | ~30μs |
| Context switch | ~0.2μs | ~1-2μs |
| Scheduling | User-space (Go runtime) | Kernel |
| Communication | Channels | Shared memory |
func workerPool(jobs <-chan Job, numWorkers int) <-chan Result {
results := make(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 <- process(job)
}
}()
}
go func() {
wg.Wait()
close(results)
}()
return results
}
func worker(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("Shutting down:", ctx.Err())
return
default:
doWork()
}
}
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
go worker(ctx)
func producer(ch chan<- int) { // Send-only
ch <- 42
}
func consumer(ch <-chan int) { // Receive-only
v := <-ch
}
// Bidirectional channels can be converted to directional
// but not the other way around
select {
case v := <-ch1: // Random selection if multiple ready
handle(v)
case ch2 <- 42:
// Sent successfully
case <-time.After(time.Second):
// Timeout
default:
// Non-blocking: executes if no channel is ready
}
// Interface: set of method signatures
type Reader interface {
Read(p []byte) (n int, err error)
}
// Implicit satisfaction: any type with Read method satisfies Reader
type File struct{}
func (f *File) Read(p []byte) (int, error) { /* ... */ }
// Empty interface: satisfied by any type
var anything interface{} = 42
// Type assertion
v, ok := anything.(int)
if ok {
fmt.Println(v) // 42
}
// Type switch
switch v := anything.(type) {
case int:
fmt.Println("int:", v)
case string:
fmt.Println("string:", v)
}
type Counter struct { n int }
// Value receiver: operates on a copy
func (c Counter) Value() int { return c.n }
// Pointer receiver: operates on the original
func (c *Counter) Increment() { c.n++ }
// Rule: if any method has pointer receiver, all should
// Interface satisfaction: *T satisfies interface with value methods
// but T does NOT satisfy interface with pointer methods
// Type parameters
func Map[T any, 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 constraints
type Number interface {
~int | ~float64
}
func Sum[T Number](nums []T) T {
var total T
for _, n := range nums {
total += n
}
return total
}
// Errors are values
type error interface {
Error() string
}
// Custom errors
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("%s: %s", e.Field, e.Message)
}
// Error wrapping (Go 1.13+)
if err != nil {
return fmt.Errorf("failed to open file: %w", err)
}
// Sentinel errors
var ErrNotFound = errors.New("not found")
// errors.Is and errors.As
if errors.Is(err, ErrNotFound) { /* ... */ }
var ve *ValidationError
if errors.As(err, &ve) { /* ... */ }
| Use Errors | Use panic |
| Expected failures | Unrecoverable errors |
| User input validation | Programmer errors (bugs) |
| Network/IO failures | Init failures |
| Business logic errors | Out of memory |
// recover() catches panics
func safeDiv(a, b int) (result int, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("panic: %v", r)
}
}()
return a / b, nil
}
- Reuse objects —
sync.Pool
- Preallocate slices —
make([]T, 0, capacity)
- Avoid allocations in hot paths — Stack allocation when possible
- Use value types — Avoid unnecessary pointer indirection
- Reduce pointer density — GC must scan pointers
# CPU profiling
go test -cpuprofile cpu.prof -bench .
go tool pprof cpu.prof
# Memory profiling
go test -memprofile mem.prof -bench .
go tool pprof mem.prof
# HTTP profiling
import _ "net/http/pprof"
go http.ListenAndServe(":6060", nil)
// The compiler decides if a variable lives on stack or heap
func foo() *int {
x := 42 // x escapes: returned pointer
return &x // heap allocated
}
func bar() int {
x := 42 // x doesn't escape
return x // stack allocated
}
// Check: go build -gcflags="-m" main.go
// Token bucket
type RateLimiter struct {
tokens chan struct{}
ticker *time.Ticker
}
func NewRateLimiter(rate int, burst int) *RateLimiter {
rl := &RateLimiter{
tokens: make(chan struct{}, burst),
ticker: time.NewTicker(time.Second / time.Duration(rate)),
}
// Fill initial burst
for i := 0; i < burst; i++ {
rl.tokens <- struct{}{}
}
go rl.refill()
return rl
}
func (rl *RateLimiter) refill() {
for range rl.ticker.C {
select {
case rl.tokens <- struct{}{}:
default: // Bucket full
}
}
}
func (rl *RateLimiter) Allow() bool {
select {
case <-rl.tokens:
return true
default:
return false
}
}
func main() {
srv := &http.Server{Addr: ":8080"}
// Channel for shutdown signal
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
// Start server
go func() {
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
log.Fatal(err)
}
}()
<-quit // Wait for signal
log.Println("Shutting down...")
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal(err)
}
log.Println("Server stopped")
}