Go Interview Reference
Fundamentals → Concurrency → DSA Patterns → Traps → Stdlib · Staff+ depth
Variables & Types
Named storage locations bound to a specific type. Go is statically typed — the compiler knows every variable's type at compile time. Zero values are guaranteed; there is no uninitialised memory.
Labelled boxes in a warehouse. The label (type) dictates what shape of item fits inside. You can relabel a box only by explicitly converting its contents — never silently.
Small, short-lived variables live on the goroutine's stack. Variables whose address escapes (returned pointer, captured by closure) are moved to the heap by the compiler's escape analysis. Zero values are written by the runtime before any code runs.
Use := inside functions for brevity. Use var at package level or when the zero value is the intent. Use const for compile-time fixed values and iota for enum-style sequences.
package main
import "fmt"
func main() {
// var declaration — explicit type
var x int = 10
var s string = "hello"
// Short declaration — type inferred (ONLY inside functions)
y := 42
name := "gopher"
// Multiple assignment
a, b := 1, 2
a, b = b, a // swap — no temp variable needed
// Zero values (default when declared without init)
var i int // 0
var f float64 // 0.0
var bl bool // false
var str string // ""
var p *int // nil
// Constants
const Pi = 3.14159
const (
KB = 1024
MB = KB * 1024
GB = MB * 1024
)
// iota — auto-incrementing constant
const (
Sunday = iota // 0
Monday // 1
Tuesday // 2
)
fmt.Println(x, s, y, name, a, b, i, f, bl, str, p, Pi, KB, Sunday, Monday, Tuesday)
}
| Type | Zero | Notes |
|---|---|---|
int | 0 | Platform size (64-bit on 64-bit systems). Use int64 explicitly for interviews. |
int8/16/32/64 | 0 | Prefer int64 to avoid overflow bugs |
float32/64 | 0.0 | Use float64 by default |
bool | false | |
string | "" | Immutable byte slice; len(s) = bytes not runes |
byte | 0 | Alias for uint8 |
rune | 0 | Alias for int32, Unicode code point |
*T | nil | Pointer to T |
// Go is STRICT — no implicit conversion
var i int = 42
var f float64 = float64(i) // explicit cast
var u uint = uint(f)
// string ↔ int via strconv (NOT string(65) = "A")
import "strconv"
n, err := strconv.Atoi("123") // string → int
s := strconv.Itoa(n) // int → string
// string ↔ []byte
bs := []byte("hello") // string → []byte (copy)
str := string(bs) // []byte → string (copy)
// string ↔ []rune (for Unicode)
runes := []rune("日本語") // len = 3
bytes := []byte("日本語") // len = 9
string(65) gives "A", not "65". Always use strconv.Itoa for int→string.
Strings & Bytes
An immutable sequence of bytes. Go strings are UTF-8 encoded, but the language exposes them at the byte level by default. A rune (int32) represents a single Unicode code point.
A sealed printed letter — you can read it and make photocopies, but you can't erase and rewrite it in place. To edit, you open the envelope ([]byte conversion), make changes, then re-seal it into a new string.
A string header is just (pointer, length) — two words. Slicing creates a new header sharing the same backing bytes (no copy). range decodes UTF-8 and yields (byteIndex, rune) pairs. Direct index s[i] yields a raw byte.
Use []byte for mutation and I/O. Use []rune only when you need character-level indexing on non-ASCII input. Use strings.Builder to concatenate in loops — never s += piece which is O(n²).
package main
import "fmt"
func main() {
s := "hello, 世界"
// len = bytes, not runes
fmt.Println(len(s)) // 13 (not 9)
fmt.Println(len([]rune(s))) // 9
// Iterate bytes (WRONG for Unicode)
for i := 0; i < len(s); i++ {
fmt.Printf("%d: %c\n", i, s[i]) // s[i] is byte
}
// Iterate runes (CORRECT)
for i, r := range s {
fmt.Printf("%d: %c (%d)\n", i, r, r)
}
// Slicing is byte-based → can corrupt multi-byte runes
fmt.Println(s[:5]) // "hello" — safe, all ASCII
// s[:8] would be unsafe if cutting mid-rune
// String is immutable; convert to []byte to modify
b := []byte(s)
b[0] = 'H'
modified := string(b)
fmt.Println(modified) // "Hello, 世界"
// strings.Builder for efficient concatenation (O(n) not O(n²))
// See stdlib section
}
for i, v := range s — i is byte index (can jump by 2-4 for non-ASCII), v is rune. Never assume i++ on rune iteration.
Control Flow
Mechanisms to branch execution (if, switch) and repeat it (for — the only loop keyword in Go). Go's control flow is intentionally minimal but covers every use case.
Traffic infrastructure: if/switch are junctions and traffic lights that route you down one road. for is a roundabout — you keep circling until a condition lets you exit.
switch compiles to a jump table or binary search for large cases — no fall-through by default. for range on a slice copies the value into the loop variable each iteration; the index is the only way to modify the original. Map iteration order is randomised by the runtime intentionally.
Prefer switch over long if/else if chains. Use for range over slices/maps/channels/strings. Use the if err := f(); err != nil init-statement pattern to keep error variables scoped.
package main
import "fmt"
func main() {
// ── if / else if / else ─────────────────────────────
x := 10
if x > 5 {
fmt.Println("big")
} else if x == 5 {
fmt.Println("five")
} else {
fmt.Println("small")
}
// if with init statement (common in Go)
if n, err := someFunc(); err == nil {
fmt.Println(n)
}
// ── switch ──────────────────────────────────────────
switch x {
case 1, 2:
fmt.Println("one or two")
case 10:
fmt.Println("ten") // NO fallthrough by default
default:
fmt.Println("other")
}
// switch without expression = if/else chain
switch {
case x < 0:
fmt.Println("negative")
case x == 0:
fmt.Println("zero")
default:
fmt.Println("positive")
}
// ── for (the ONLY loop in Go) ───────────────────────
// C-style
for i := 0; i < 5; i++ {
fmt.Println(i)
}
// while-style
n := 0
for n < 5 {
n++
}
// infinite loop
for {
break // or continue
}
// range over slice
nums := []int{1, 2, 3}
for i, v := range nums {
fmt.Println(i, v)
}
// range over map (ORDER NOT GUARANTEED)
m := map[string]int{"a": 1, "b": 2}
for k, v := range m {
fmt.Println(k, v)
}
// range over string gives runes
for i, r := range "hello" {
fmt.Println(i, r)
}
// range over channel
ch := make(chan int, 3)
ch <- 1; ch <- 2; ch <- 3; close(ch)
for v := range ch { // exits when channel closed
fmt.Println(v)
}
}
func someFunc() (int, error) { return 42, nil }
Slices
A dynamic, resizable view over an underlying fixed array. The fundamental Go sequence type — arrays are rarely used directly. A slice is a 3-field struct: pointer to array, length, capacity.
A camera viewfinder over a landscape. Multiple viewfinders can look at overlapping parts of the same scene (shared backing array). Zooming in/out changes what you see but not the landscape itself — until you actually edit it.
append returns a new slice header. If len < cap, it extends in-place (still shares array!). If len == cap, a new larger array is allocated and data is copied. Growth: 2× below 1024 elements, then ~1.25× above.
Default choice for ordered collections. Pre-allocate with make([]T, 0, n) when size is known — avoids repeated reallocation. Use copy() explicitly whenever you need an independent clone of a sub-slice.
Slices are the most important data structure in Go. A slice is a 3-field header: pointer to underlying array, length, capacity.
package main
import "fmt"
func main() {
// Creation
var s []int // nil slice (len=0, cap=0)
s1 := []int{1, 2, 3} // literal
s2 := make([]int, 5) // len=5, cap=5, all zeros
s3 := make([]int, 3, 10) // len=3, cap=10
_ = s; _ = s2; _ = s3
// append
s1 = append(s1, 4, 5) // append multiple
s1 = append(s1, s2...) // append slice
// Slicing [low:high] — shares underlying array!
a := []int{0, 1, 2, 3, 4, 5}
b := a[1:4] // [1 2 3], cap=5
b[0] = 99 // modifies a[1] too!
fmt.Println(a) // [0 99 2 3 4 5]
// copy to avoid aliasing
c := make([]int, len(b))
copy(c, b)
c[0] = 0 // does NOT affect a
// 2D slice
matrix := make([][]int, 3)
for i := range matrix {
matrix[i] = make([]int, 3)
}
// Delete element at index i (preserve order)
i := 2
a = append(a[:i], a[i+1:]...)
// Delete element (fast — swap with last)
a[i] = a[len(a)-1]
a = a[:len(a)-1]
// Stack operations
stack := []int{}
stack = append(stack, 1) // push
top := stack[len(stack)-1] // peek
stack = stack[:len(stack)-1] // pop
_ = top
fmt.Println(len(s1), cap(s1))
}
b := a[1:4]) shares the underlying array. Modifying b modifies a. Use copy() when you need independence. Also: append may or may not allocate — never assume it creates a new slice.
make([]T, 0, n) when you know the size.
Maps
An unordered hash table mapping comparable keys to values. Go's built-in map provides O(1) average-case get, set, and delete. Keys must be comparable (support ==) — slices, maps, and functions cannot be keys.
A coat-check room with numbered tickets. You hand in your coat (value) and get a ticket (key). Retrieval is instant — the attendant goes directly to the hanger, regardless of how full the room is.
Go maps use a hash table with 8-element buckets. On overflow, overflow buckets are chained. Load factor triggers rehashing. Iteration order is randomised every program run to prevent reliance on ordering. A nil map reads safely (returns zero value) but panics on write.
Frequency counting (freq[x]++), membership testing (map[T]struct{} set), grouping, caching/memoisation, and anagram/pattern detection. Always initialise with make or a literal before writing.
package main
import "fmt"
func main() {
// Creation — always use make or literal
m := make(map[string]int)
m2 := map[string]int{"a": 1, "b": 2}
_ = m2
// CRUD
m["key"] = 42 // set
v := m["key"] // get (returns zero value if missing)
delete(m, "key") // delete (no-op if key absent)
// Comma-ok idiom — check existence
val, ok := m["key"]
if !ok {
fmt.Println("not found")
}
_ = val
// Common patterns
// Frequency counter
words := []string{"a", "b", "a", "c", "b", "a"}
freq := make(map[string]int)
for _, w := range words {
freq[w]++ // zero value of int is 0, so safe
}
// Set (map[T]struct{} uses less memory than map[T]bool)
seen := make(map[string]struct{})
seen["x"] = struct{}{}
_, exists := seen["x"]
_ = exists
// Map of slices
groups := make(map[int][]string)
groups[1] = append(groups[1], "a")
groups[1] = append(groups[1], "b")
fmt.Println(freq, groups)
}
var m map[string]int; m["k"] = 1 // PANIC. Always initialize with make or a literal. Reading from a nil map is safe (returns zero value).
sync.RWMutex or sync.Map. This is a common Staff+ follow-up.
Functions
Named, reusable blocks of code that accept inputs and return outputs. Functions are first-class values in Go — they can be stored in variables, passed as arguments, and returned from other functions.
A recipe card. It defines steps (body), accepts ingredients (params), and produces a dish — plus a note if something went wrong (multiple return values, idiomatic error as last return). You can hand the card to anyone or pin it to the fridge (first-class/closure).
Arguments are passed by value (copies). Closures capture outer variables by reference to their memory location — the captured variable lives on the heap if it outlives the enclosing scope. defer pushes calls onto a LIFO stack within the current goroutine stack frame, executed on any return path.
Closures for stateful callbacks, memoisation wrappers, and the var dfs func(...) recursive pattern. Variadic (...T) for flexible APIs. Named returns only when defer needs to modify the return value — avoid otherwise (reduces clarity).
package main
import (
"errors"
"fmt"
)
// ── Basic: multiple return values ────────────────────────
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
// ── Named returns (use sparingly, good for defer) ────────
func minMax(nums []int) (min, max int) {
min, max = nums[0], nums[0]
for _, n := range nums[1:] {
if n < min { min = n }
if n > max { max = n }
}
return // naked return — returns named vars
}
// ── Variadic ─────────────────────────────────────────────
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
// ── First-class functions & closures ─────────────────────
func makeCounter() func() int {
count := 0
return func() int { // closes over count
count++
return count
}
}
// ── Higher-order functions ────────────────────────────────
func filter(nums []int, predicate func(int) bool) []int {
result := make([]int, 0)
for _, n := range nums {
if predicate(n) {
result = append(result, n)
}
}
return result
}
// ── Defer ─────────────────────────────────────────────────
func deferDemo() {
// Defers execute LIFO when function returns
defer fmt.Println("third") // printed last
defer fmt.Println("second")
defer fmt.Println("first") // printed first
fmt.Println("body")
}
// ── Defer with named return (advanced) ───────────────────
func readFile() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered: %v", r) // modifies named return
}
}()
panic("something went wrong")
}
func main() {
r, err := divide(10, 3)
fmt.Printf("%.2f %v\n", r, err)
mn, mx := minMax([]int{3, 1, 4, 1, 5, 9})
fmt.Println(mn, mx)
fmt.Println(sum(1, 2, 3))
s := []int{1, 2, 3}
fmt.Println(sum(s...)) // unpack slice into variadic
counter := makeCounter()
fmt.Println(counter(), counter(), counter()) // 1 2 3
evens := filter([]int{1,2,3,4,5,6}, func(n int) bool { return n%2 == 0 })
fmt.Println(evens) // [2 4 6]
deferDemo()
fmt.Println(readFile())
}
defer fmt.Println(x) captures x's value now. Use a closure defer func() { fmt.Println(x) }() to capture by reference.
for { defer f() } — all defers stack up, run when function exits (not each iteration). Wrap in helper function or IIFE: func() { defer f(); body() }()
Pointers
A variable that holds the memory address of another variable. Dereferencing a pointer gives you the value at that address. Go pointers are type-safe — a *int can only point to an int. No pointer arithmetic.
A postal address. Instead of mailing someone a copy of your entire house (pass by value), you give them the address. They can visit and rearrange the furniture (mutate). nil is an address with no house — ring the bell and crash.
&x takes the address of x, returning *T. *p dereferences to get/set the value. If a pointer escapes its declaring scope (returned or stored), the compiler moves the pointee to the heap via escape analysis. Struct pointer fields are auto-dereferenced: p.Field is sugar for (*p).Field.
Pointer receiver methods when the method mutates the receiver or the struct is large (avoid copying). Pass pointers to functions that need to modify a value. Use *T as an optional type (nil = absent). Avoid pointer-heavy code in hot paths — cache misses are expensive.
package main
import "fmt"
func increment(n *int) {
*n++ // dereference and modify
}
func newInt(n int) *int {
return &n // safe: Go's GC keeps n alive (escapes to heap)
}
func main() {
x := 10
p := &x // p is *int, &x takes address
fmt.Println(*p) // 10 — dereference
*p = 20 // modify via pointer
fmt.Println(x) // 20
increment(&x)
fmt.Println(x) // 21
// new() allocates and returns pointer to zero value
q := new(int) // *int pointing to 0
*q = 5
// Pointer to struct — auto-dereference
type Point struct{ X, Y int }
pt := &Point{1, 2}
pt.X = 10 // equivalent to (*pt).X = 10
// nil pointer
var nilP *int
fmt.Println(nilP) //
// *nilP = 1 ← PANIC: nil pointer dereference
}
Structs, Methods & Interfaces
Struct: a composite type grouping named fields of different types — Go's primary data-modelling tool. Methods: functions with a receiver, associating behaviour with a type. Interface: an implicit contract of method signatures — satisfied automatically, not declared.
Struct is a blueprint; a value is a house built from it. An interface is a job description — anyone who can perform the listed duties qualifies, regardless of background. Embedding is like hiring a specialist (Animal) inside a broader role (Dog) — their skills are promoted to the surface.
Interfaces store a (type pointer, value pointer) pair — an interface is never truly nil unless both are nil. Struct fields are laid out sequentially in memory with alignment padding. Embedding copies the embedded type's method set into the outer type's method set at compile time.
Define interfaces at the consumer site, not the producer (Go proverb). Use pointer receivers consistently across a type. Use interface{} / any sparingly — prefer generics (Go 1.18+) for type-safe polymorphism. Type switches for runtime dispatch on interface values.
package main
import (
"fmt"
"math"
)
// ── Struct ────────────────────────────────────────────────
type Shape interface {
Area() float64
Perimeter() float64
}
type Circle struct {
Radius float64
}
type Rectangle struct {
Width, Height float64
}
// ── Methods ───────────────────────────────────────────────
// Pointer receiver — can mutate, avoids copy
func (c *Circle) Area() float64 { return math.Pi * c.Radius * c.Radius }
func (c *Circle) Perimeter() float64 { return 2 * math.Pi * c.Radius }
func (c *Circle) Scale(f float64) { c.Radius *= f }
func (r Rectangle) Area() float64 { return r.Width * r.Height }
func (r Rectangle) Perimeter() float64 { return 2 * (r.Width + r.Height) }
// ── Embedding (Go's inheritance alternative) ─────────────
type Animal struct{ Name string }
func (a Animal) Speak() string { return a.Name + " speaks" }
type Dog struct {
Animal // embedded — Dog promotes Animal's methods
Breed string
}
// ── Interface satisfaction is implicit ───────────────────
func printShape(s Shape) {
fmt.Printf("Area: %.2f, Perimeter: %.2f\n", s.Area(), s.Perimeter())
}
// ── Empty interface & type assertion ─────────────────────
func describe(i interface{}) {
// Type assertion
if s, ok := i.(string); ok {
fmt.Println("string:", s)
return
}
// Type switch
switch v := i.(type) {
case int: fmt.Println("int:", v)
case float64: fmt.Println("float64:", v)
case []int: fmt.Println("[]int, len:", len(v))
default: fmt.Printf("unknown: %T\n", v)
}
}
func main() {
c := &Circle{Radius: 5}
r := Rectangle{Width: 4, Height: 6}
printShape(c) // *Circle satisfies Shape
printShape(r) // Rectangle satisfies Shape (value receiver)
// printShape(&r) also works
c.Scale(2)
fmt.Println(c.Radius) // 10
d := Dog{Animal: Animal{Name: "Rex"}, Breed: "Lab"}
fmt.Println(d.Speak()) // promoted method: "Rex speaks"
fmt.Println(d.Name) // promoted field
describe(42)
describe("hello")
describe(3.14)
describe([]int{1,2,3})
// Struct literal
_ = Circle{Radius: 1} // named
_ = Circle{1} // positional (avoid in production)
}
var c *Circle = nil; var s Shape = c; s == nil // FALSE! The interface holds (type=*Circle, value=nil). Check with reflect.ValueOf(s).IsNil() or restructure to return untyped nil.
func Map[T, U any](s []T, f func(T) U) []U — use type parameters for reusable algorithms. Common constraints: comparable, constraints.Ordered from golang.org/x/exp/constraints.
Goroutines
A lightweight, concurrently executing function managed by the Go runtime scheduler — not a direct OS thread. The go keyword spawns one. Goroutines are Go's core concurrency primitive.
Waitstaff in a busy restaurant. Many servers (goroutines) work simultaneously, each handling a table (task). A small manager team (OS threads, controlled by GOMAXPROCS) executes the actual work. When a server waits for the kitchen (I/O block), the manager serves another table.
Each goroutine starts with a 2KB stack that grows and shrinks dynamically. The runtime uses M:N scheduling — M goroutines multiplexed onto N OS threads. A goroutine blocked on I/O or channel ops yields the OS thread to another runnable goroutine. GOMAXPROCS (default = CPU count) controls parallelism.
Any concurrent work: I/O fan-out, background processing, parallel computation. Always answer "who waits for this goroutine?" — use sync.WaitGroup or a done channel. Never fire-and-forget if the goroutine touches shared state or can panic.
Goroutines are lightweight (2KB initial stack, grows as needed). The runtime multiplexes them onto OS threads (GOMAXPROCS). Always think: who waits for this goroutine to finish?
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done() // signal completion even on panic
fmt.Printf("Worker %d starting\n", id)
time.Sleep(10 * time.Millisecond) // simulate work
fmt.Printf("Worker %d done\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1)
go worker(i, &wg)
}
wg.Wait() // blocks until all Done() calls
fmt.Println("All workers done")
}
for i := 0; i < 5; i++ { go func() { fmt.Println(i) }() } — all goroutines print 5 (the final value). Fix: go func(i int) { fmt.Println(i) }(i) or i := i shadowing inside loop. (Go 1.22+ fixes this by default.)
context.WithCancel or a done channel for long-running goroutines.
Channels
A typed conduit for communicating between goroutines, combining synchronisation and data transfer. Go's concurrency philosophy: "Do not communicate by sharing memory; share memory by communicating."
An unbuffered channel is a direct handoff — like handing a baton in a relay race. Both runners must be ready simultaneously. A buffered channel is a conveyor belt with limited capacity — the sender places items, the receiver picks them up, but the belt can hold only N items before blocking the sender.
Channels are implemented as a ring buffer (buffered) or a direct goroutine wakeup (unbuffered). Blocked goroutines are parked on the channel's send/recv queue and rescheduled by the runtime when space/data is available. close() marks the channel done; buffered data is still drainable. Sending to a closed channel panics.
Ownership transfer of data between goroutines, pipeline stages, done/cancellation signalling (close(done) broadcasts to all receivers), fan-in merging. Use directional types (chan<-, <-chan) in function signatures to enforce intent.
package main
import "fmt"
func main() {
// Unbuffered — sender blocks until receiver ready (synchronous)
ch := make(chan int)
go func() { ch <- 42 }() // goroutine blocks until we receive
v := <-ch
fmt.Println(v) // 42
// Buffered — sender blocks only when buffer full
bch := make(chan string, 3)
bch <- "a"; bch <- "b"; bch <- "c"
// bch <- "d" would block here (buffer full)
fmt.Println(<-bch) // "a" (FIFO)
// close signals "no more values"
// range over channel reads until closed
jobs := make(chan int, 5)
for i := 0; i < 5; i++ { jobs <- i }
close(jobs)
for j := range jobs { // exits when closed AND empty
fmt.Println(j)
}
// Check if closed (comma-ok)
ch2 := make(chan int, 1)
ch2 <- 1; close(ch2)
v1, ok := <-ch2 // ok=true, v1=1
v2, ok := <-ch2 // ok=false, v2=0 (zero value after close)
fmt.Println(v1, ok, v2)
// Directional types (enforce in function signatures)
ping := make(chan string, 1)
pong := make(chan string, 1)
go pingFunc(ping, pong) // send-only ping, recv-only pong
ping <- "passed message"
result := <-pong
fmt.Println(result)
}
// chan<- = send only, <-chan = receive only
func pingFunc(in <-chan string, out chan<- string) {
msg := <-in
out <- msg + " pong"
}
| Operation | Nil ch | Open empty | Open with data | Closed empty | Closed with data |
|---|---|---|---|---|---|
| Receive | Block | Block | Value | Zero, false | Value, true |
| Send | Block | Block (unbuf) | Send or block | PANIC | PANIC |
| Close | PANIC | Closes ok | Closes ok | PANIC | PANIC |
Select
A control structure that waits on multiple channel operations simultaneously, executing whichever case becomes ready first. Go's mechanism for multiplexing concurrent communication.
A call centre agent with multiple phone lines. They pick up whichever line rings first. If no lines are ringing and there's a default case, they do admin work instead of waiting. If two lines ring at once, they pick one at random.
The runtime evaluates all case channel expressions, then atomically checks each for readiness. If multiple are ready, one is selected uniformly at random. If none are ready and there is no default, the goroutine parks. default makes select non-blocking.
Timeout: case <-time.After(d). Cancellation: case <-ctx.Done(). Non-blocking probe: default. Fan-in: receive from N channels. Always include a ctx.Done() case in long-running selects to prevent goroutine leaks.
package main
import (
"context"
"fmt"
"time"
)
func main() {
ch1 := make(chan string, 1)
ch2 := make(chan string, 1)
ch1 <- "one"
ch2 <- "two"
// select picks ready channel randomly if multiple ready
select {
case msg := <-ch1:
fmt.Println("ch1:", msg)
case msg := <-ch2:
fmt.Println("ch2:", msg)
}
// Non-blocking receive (default case)
select {
case v := <-ch1:
fmt.Println("received:", v)
default:
fmt.Println("no value ready")
}
// Timeout pattern
resultCh := make(chan int, 1)
go func() {
time.Sleep(50 * time.Millisecond)
resultCh <- 42
}()
select {
case res := <-resultCh:
fmt.Println("got:", res)
case <-time.After(100 * time.Millisecond):
fmt.Println("timeout!")
}
// Context cancellation pattern
ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
defer cancel()
select {
case res := <-resultCh:
fmt.Println("result:", res)
case <-ctx.Done():
fmt.Println("context:", ctx.Err())
}
}
sync Package
Package providing low-level shared-memory synchronisation primitives: Mutex, RWMutex, WaitGroup, Once, Map, Cond, and the atomic sub-package for lock-free integer ops.
Mutex: a single toilet key passed between co-workers — only one at a time. RWMutex: a library — many people can read simultaneously, but writing requires clearing the room. WaitGroup: a project manager waiting for all contractors to sign off before closing a job.
Mutex uses a futex — fast userspace spin, only a syscall when contended. WaitGroup uses an atomic counter; Wait() parks until counter hits zero. Once combines atomic flag with a mutex for the one-time execution guarantee. atomic uses CPU-level compare-and-swap instructions — no lock needed.
Mutex: protecting a struct with mixed reads/writes. RWMutex: read-heavy workloads (8:1+ ratio). WaitGroup: wait for N goroutines to finish. Once: lazy singleton init. atomic: single integer counter shared across goroutines. sync.Map: stable key sets with high concurrent read throughput.
package main
import (
"fmt"
"sync"
"sync/atomic"
)
// ── Mutex ──────────────────────────────────────────────────
type SafeCounter struct {
mu sync.Mutex
count int
}
func (c *SafeCounter) Inc() {
c.mu.Lock()
defer c.mu.Unlock() // always defer to avoid forgetting unlock
c.count++
}
func (c *SafeCounter) Value() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.count
}
// ── RWMutex (read-heavy workloads) ────────────────────────
type Cache struct {
mu sync.RWMutex
data map[string]string
}
func (c *Cache) Get(key string) (string, bool) {
c.mu.RLock() // multiple concurrent readers allowed
defer c.mu.RUnlock()
v, ok := c.data[key]
return v, ok
}
func (c *Cache) Set(key, val string) {
c.mu.Lock() // exclusive write lock
defer c.mu.Unlock()
c.data[key] = val
}
// ── Once (singleton / one-time init) ─────────────────────
var (
once sync.Once
instance *SafeCounter
)
func getInstance() *SafeCounter {
once.Do(func() {
instance = &SafeCounter{}
})
return instance
}
// ── sync.Map (concurrent map, read-heavy) ────────────────
func syncMapDemo() {
var sm sync.Map
sm.Store("key", "value")
v, ok := sm.Load("key")
fmt.Println(v, ok)
sm.Delete("key")
sm.Range(func(k, v any) bool {
fmt.Println(k, v)
return true // continue iteration
})
}
// ── atomic (lock-free integer ops) ───────────────────────
func atomicDemo() {
var counter int64
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
atomic.AddInt64(&counter, 1)
}()
}
wg.Wait()
fmt.Println(atomic.LoadInt64(&counter)) // 1000
}
func main() {
c := &SafeCounter{}
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func() { defer wg.Done(); c.Inc() }()
}
wg.Wait()
fmt.Println(c.Value()) // 100
cache := &Cache{data: make(map[string]string)}
cache.Set("go", "awesome")
v, _ := cache.Get("go")
fmt.Println(v)
fmt.Println(getInstance() == getInstance()) // true — same instance
syncMapDemo()
atomicDemo()
}
sync.Mutex: general mutual exclusion. sync.RWMutex: read-heavy (8:1+ reads). sync.Map: stable key sets with high concurrent reads. atomic: single-variable counters (lock-free). Channels: coordination and ownership transfer.
Context
An immutable value that carries deadlines, cancellation signals, and request-scoped data across API and goroutine boundaries. The standard Go mechanism for graceful shutdown and propagating cancellation through a call tree.
A project brief handed down through a chain of contractors. If the client cancels the project, every sub-contractor checks their copy of the brief and stops work. The brief also carries a deadline: if it passes, everyone stops regardless of client instructions.
Contexts form an immutable tree. Cancelling a parent atomically closes its Done() channel and propagates cancellation to all children. Each WithCancel/WithTimeout/WithDeadline creates a child node. Done() returns a channel; Err() returns context.Canceled or context.DeadlineExceeded.
Pass as first argument to any function doing I/O, DB queries, RPCs, or long computation. Always defer cancel() to release resources. Use WithValue only for request-scoped metadata (trace IDs, auth tokens) — never for optional function parameters.
package main
import (
"context"
"fmt"
"time"
)
func doWork(ctx context.Context, id int) error {
select {
case <-time.After(100 * time.Millisecond): // simulate work
fmt.Printf("worker %d done\n", id)
return nil
case <-ctx.Done():
return ctx.Err() // context.DeadlineExceeded or context.Canceled
}
}
func main() {
// WithCancel — manual cancellation
ctx, cancel := context.WithCancel(context.Background())
go func() {
time.Sleep(50 * time.Millisecond)
cancel() // trigger cancellation
}()
fmt.Println(doWork(ctx, 1)) // context canceled
// WithTimeout
ctx2, cancel2 := context.WithTimeout(context.Background(), 200*time.Millisecond)
defer cancel2() // ALWAYS defer cancel to free resources
fmt.Println(doWork(ctx2, 2)) // done (200ms > 100ms)
// WithDeadline
deadline := time.Now().Add(30 * time.Millisecond)
ctx3, cancel3 := context.WithDeadline(context.Background(), deadline)
defer cancel3()
fmt.Println(doWork(ctx3, 3)) // deadline exceeded
// WithValue — pass request-scoped values (NOT for optional params!)
type ctxKey string
ctx4 := context.WithValue(context.Background(), ctxKey("requestID"), "abc-123")
reqID := ctx4.Value(ctxKey("requestID")).(string)
fmt.Println("requestID:", reqID)
}
defer cancel() to avoid resource leaks. (4) Use typed keys for WithValue to avoid collisions.
Concurrency Patterns
Reusable structural blueprints for orchestrating goroutines. The three foundational patterns are Worker Pool (bounded parallelism), Pipeline (streaming stages), and Fan-Out/Fan-In (parallel work + result merge).
Factory floor layouts: a worker pool is shift workers sharing machines — N tasks, M workers, M ≤ N. A pipeline is an assembly line — each stage does one job and passes to the next. Fan-out/fan-in is splitting a shipment among multiple warehouses then consolidating at one distribution centre.
Worker pool: buffered job channel consumed by N goroutines + WaitGroup. Pipeline: each stage owns an input and output channel, closed when done. Fan-out: N goroutines reading from one channel. Fan-in: merge goroutine reads from N channels into one, closes output after WaitGroup finishes.
Worker pool for rate-limiting API calls, CPU-bound tasks, database writes. Pipeline for streaming ETL, log processing, image transforms. Fan-out/fan-in when N independent tasks produce results that must be merged — avoids O(N) sequential waiting.
★ Staff+ Worker Pool
Use when: bounded parallelism, rate-limiting external calls, CPU-bound tasks
package main
import (
"fmt"
"sync"
)
func workerPool(numWorkers int, jobs []int) []int {
jobCh := make(chan int, len(jobs))
resultCh := make(chan int, len(jobs))
// Start workers
var wg sync.WaitGroup
for w := 0; w < numWorkers; w++ {
wg.Add(1)
go func() {
defer wg.Done()
for job := range jobCh { // exit when channel closed
resultCh <- job * job // simulate work
}
}()
}
// Send jobs
for _, j := range jobs { jobCh <- j }
close(jobCh) // signal no more jobs
// Close results when all workers done
go func() { wg.Wait(); close(resultCh) }()
// Collect results
var results []int
for r := range resultCh { results = append(results, r) }
return results
}
func main() {
results := workerPool(4, []int{1, 2, 3, 4, 5, 6, 7, 8})
fmt.Println(results)
}
★ Staff+ Pipeline
Use when: streaming multi-stage data transformations
package main
import "fmt"
func generate(nums ...int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for _, n := range nums { out <- n }
}()
return out
}
func square(in <-chan int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for n := range in { out <- n * n }
}()
return out
}
func double(in <-chan int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for n := range in { out <- n * 2 }
}()
return out
}
func main() {
// Chain stages: generate → square → double
out := double(square(generate(1, 2, 3, 4, 5)))
for v := range out {
fmt.Println(v) // 2, 8, 18, 32, 50
}
}
★ Staff+ Fan-Out / Fan-In
package main
import (
"fmt"
"sync"
)
// Fan-out: distribute work across N goroutines
// Fan-in: merge N channels into one
func merge(channels ...<-chan int) <-chan int {
var wg sync.WaitGroup
merged := make(chan int, 10)
output := func(c <-chan int) {
defer wg.Done()
for v := range c { merged <- v }
}
wg.Add(len(channels))
for _, c := range channels { go output(c) }
go func() { wg.Wait(); close(merged) }()
return merged
}
func fanOut(in <-chan int, n int) []<-chan int {
channels := make([]<-chan int, n)
for i := 0; i < n; i++ {
ch := make(chan int, 5)
channels[i] = ch
go func(out chan<- int) {
defer close(out)
for v := range in { out <- v * v }
}(ch)
}
return channels
}
func main() {
in := make(chan int, 10)
for i := 1; i <= 10; i++ { in <- i }
close(in)
workers := fanOut(in, 3)
for v := range merge(workers...) {
fmt.Println(v)
}
}
Two Pointers
Two index variables scanning a sorted array or string — either from opposite ends (converging) or in the same direction (fast/slow) — to find pairs, subarrays, or positions that satisfy a condition in O(n) instead of O(n²).
Two people walking toward each other on a number line. If their combined position overshoots the target, the right person steps left. If it undershoots, the left person steps right. They meet exactly at the answer.
Relies on the sorted invariant: moving the left pointer right increases the sum; moving the right pointer left decreases it. One pass suffices because each pointer moves monotonically — no element is visited twice, giving O(n).
Sorted array + target sum/difference. Palindrome check. Remove duplicates in-place. Container with most water. Three-sum (sort first, then two-pointer inside). Any "find pair" or "shrink from both ends" problem.
l=0, r=len-1. Evaluate the pair. If condition is met → record and move both. If sum/value too small → advance l. If too large → retreat r. Loop while l < r.Use when: sorted array, find pair with sum, remove duplicates, palindrome check.
package main
import "fmt"
// Two Sum (sorted array)
func twoSumSorted(nums []int, target int) (int, int) {
l, r := 0, len(nums)-1
for l < r {
sum := nums[l] + nums[r]
switch {
case sum == target: return l, r
case sum < target: l++
default: r--
}
}
return -1, -1
}
// Valid palindrome
func isPalindrome(s string) bool {
l, r := 0, len(s)-1
for l < r {
if s[l] != s[r] { return false }
l++; r--
}
return true
}
// Container with most water
func maxWater(height []int) int {
l, r, maxArea := 0, len(height)-1, 0
for l < r {
h := min(height[l], height[r])
if area := h * (r - l); area > maxArea { maxArea = area }
if height[l] < height[r] { l++ } else { r-- }
}
return maxArea
}
// Three sum (sorted)
func threeSum(nums []int) [][3]int {
// Sort first (not shown — use sort.Ints)
var result [][3]int
for i := 0; i < len(nums)-2; i++ {
if i > 0 && nums[i] == nums[i-1] { continue } // skip dup
l, r := i+1, len(nums)-1
for l < r {
sum := nums[i] + nums[l] + nums[r]
switch {
case sum == 0:
result = append(result, [3]int{nums[i], nums[l], nums[r]})
for l < r && nums[l] == nums[l+1] { l++ }
for l < r && nums[r] == nums[r-1] { r-- }
l++; r--
case sum < 0: l++
default: r--
}
}
}
return result
}
func min(a, b int) int { if a < b { return a }; return b }
func main() {
fmt.Println(twoSumSorted([]int{1,2,3,4,6}, 6)) // 1 3
fmt.Println(isPalindrome("racecar")) // true
fmt.Println(maxWater([]int{1,8,6,2,5,4,8,3,7})) // 49
}
Sliding Window
A contiguous subarray or substring whose left and right boundaries slide rightward, maintaining a window invariant. The window expands by advancing the right pointer and shrinks by advancing the left pointer when the constraint is violated.
A physical window frame sliding along a wall of tiles. Fixed window: the frame is a fixed size, showing exactly k tiles. Variable window: the frame stretches to include as many tiles as possible while a rule is met, then contracts from the left when the rule breaks.
Maintain window state (sum, frequency map, distinct count) incrementally: add nums[right] on expand, subtract nums[left] on shrink. Each element is added once and removed at most once → O(n). The invariant check is O(1) per step if state is tracked correctly.
Keywords: "longest/shortest subarray/substring", "contiguous subarray with property", "max sum of size k". Fixed window: exactly k elements. Variable window: "at most k distinct", "sum equals target", "all chars appear".
nums[i-k], add nums[i].Variable: expand
right always; shrink left when constraint violated. Answer = right - left + 1 when valid.Fixed window — window size is constant k
// Max sum of subarray of size k
func maxSumK(nums []int, k int) int {
if len(nums) < k { return -1 }
windowSum := 0
for i := 0; i < k; i++ {
windowSum += nums[i]
}
maxSum := windowSum
for i := k; i < len(nums); i++ {
windowSum += nums[i] - nums[i-k]
if windowSum > maxSum { maxSum = windowSum }
}
return maxSum
}
Variable window — shrink/grow based on constraint
// Longest substring without repeating chars
func lengthOfLongestSubstring(s string) int {
charIdx := make(map[byte]int)
maxLen, left := 0, 0
for right := 0; right < len(s); right++ {
if idx, ok := charIdx[s[right]]; ok && idx >= left {
left = idx + 1 // shrink window
}
charIdx[s[right]] = right
if l := right - left + 1; l > maxLen { maxLen = l }
}
return maxLen
}
// Minimum window in s that contains all chars of t
func minWindow(s, t string) string {
need := make(map[byte]int)
for i := 0; i < len(t); i++ { need[t[i]]++ }
have, total := 0, len(need)
window := make(map[byte]int)
resL, resLen := 0, len(s)+1
l := 0
for r := 0; r < len(s); r++ {
c := s[r]
window[c]++
if need[c] > 0 && window[c] == need[c] { have++ }
for have == total { // valid window, shrink left
if r-l+1 < resLen { resL, resLen = l, r-l+1 }
window[s[l]]--
if need[s[l]] > 0 && window[s[l]] < need[s[l]] { have-- }
l++
}
}
if resLen == len(s)+1 { return "" }
return s[resL : resL+resLen]
}
Hashing
map[K]V is the built-in hash map. A map[T]struct{} is a hash set. Hashing converts O(n) linear scans into O(1) lookups — the single most impactful tool for reducing time complexity.Convert "is X in the collection?" from O(n) linear scan into O(1) by loading elements into a map/set first.
package main
import "fmt"
// ── Set pattern: map[T]struct{} ────────────────────────────
// struct{} uses 0 bytes vs bool which uses 1 byte per entry
func contains(slice []int, target int) bool {
set := make(map[int]struct{})
for _, v := range slice { set[v] = struct{}{} }
_, ok := set[target]
return ok
}
// ── Check if sentence is pangram ──────────────────────────
// Every letter a-z appears at least once
func checkIfPangram(sentence string) bool {
seen := make(map[rune]bool)
for _, c := range sentence { seen[c] = true }
return len(seen) >= 26
}
// Bit manipulation variant — O(1) space
func checkIfPangramBits(sentence string) bool {
var mask int
for _, c := range sentence {
if c >= 'a' && c <= 'z' {
mask |= 1 << (c - 'a')
}
}
return mask == (1<<26)-1
}
// ── Missing number: find what's absent from [0..n] ────────
func missingNumber(nums []int) int {
set := make(map[int]bool)
for _, n := range nums { set[n] = true }
for i := 0; i <= len(nums); i++ {
if !set[i] { return i }
}
return -1
}
// Math trick — O(1) space: expected sum minus actual sum
func missingNumberMath(nums []int) int {
n := len(nums)
expected := n * (n + 1) / 2
actual := 0
for _, v := range nums { actual += v }
return expected - actual
}
// ── Counting Elements: how many x where x+1 also exists ───
func countElements(arr []int) int {
set := make(map[int]bool)
for _, v := range arr { set[v] = true }
count := 0
for _, v := range arr {
if set[v+1] { count++ }
}
return count
}
// ── Two Sum — classic hash pattern ────────────────────────
// For each num, check if (target - num) already seen
func twoSum(nums []int, target int) (int, int) {
seen := make(map[int]int) // value → index
for i, n := range nums {
if j, ok := seen[target-n]; ok {
return j, i
}
seen[n] = i
}
return -1, -1
}
// ── Two Sum II: count pairs summing to target ─────────────
func twoSumCount(nums []int, target int) int {
freq := make(map[int]int)
for _, n := range nums { freq[n]++ }
count := 0
for n, c := range freq {
complement := target - n
if complement == n {
count += c * (c - 1) / 2 // C(c, 2) pairs
} else if freq[complement] > 0 && complement > n {
count += c * freq[complement]
}
}
return count
}
func main() {
fmt.Println(checkIfPangram("thequickbrownfoxjumpsoverthelazydog")) // true
fmt.Println(checkIfPangramBits("abcdefghijklmnopqrstuvwxyz")) // true
fmt.Println(missingNumberMath([]int{3, 0, 1})) // 2
fmt.Println(countElements([]int{1, 2, 3, 3, 5, 0})) // 3
i, j := twoSum([]int{2, 7, 11, 15}, 9)
fmt.Println(i, j) // 0 1
}
Build a frequency map to answer "how many times does X appear?" or "do two collections have the same composition?"
package main
import (
"fmt"
"sort"
)
// ── Anagram check ─────────────────────────────────────────
func isAnagram(s, t string) bool {
if len(s) != len(t) { return false }
freq := make(map[rune]int)
for _, c := range s { freq[c]++ }
for _, c := range t {
freq[c]--
if freq[c] < 0 { return false }
}
return true
}
// Fixed-size array variant for lowercase only — faster
func isAnagramArray(s, t string) bool {
if len(s) != len(t) { return false }
var freq [26]int
for i := 0; i < len(s); i++ {
freq[s[i]-'a']++
freq[t[i]-'a']--
}
for _, v := range freq { if v != 0 { return false } }
return true
}
// ── Group anagrams together ───────────────────────────────
// Key insight: anagrams have the same sorted string
func groupAnagrams(words []string) [][]string {
groups := make(map[string][]string)
for _, w := range words {
key := sortedKey(w)
groups[key] = append(groups[key], w)
}
result := make([][]string, 0, len(groups))
for _, g := range groups { result = append(result, g) }
return result
}
func sortedKey(s string) string {
b := []byte(s)
sort.Slice(b, func(i, j int) bool { return b[i] < b[j] })
return string(b)
}
// ── Find players with 0 or 1 losses ──────────────────────
// losses[player] = loss count; find: 0 losses and exactly 1 loss
func findWinners(matches [][2]int) ([]int, []int) {
losses := make(map[int]int)
played := make(map[int]bool)
for _, m := range matches {
played[m[0]] = true
played[m[1]] = true
losses[m[1]]++ // only loser gets a loss
}
var noLoss, oneLoss []int
for p := range played {
switch losses[p] {
case 0: noLoss = append(noLoss, p)
case 1: oneLoss = append(oneLoss, p)
}
}
sort.Ints(noLoss); sort.Ints(oneLoss)
return noLoss, oneLoss
}
// ── Largest unique number ──────────────────────────────────
// Largest integer that appears exactly once
func largestUniqueNumber(nums []int) int {
freq := make(map[int]int)
for _, n := range nums { freq[n]++ }
result := -1
for n, c := range freq {
if c == 1 && n > result { result = n }
}
return result
}
// ── Maximum number of balloons ─────────────────────────────
// How many times can you form the word "balloon"?
func maxNumberOfBalloons(text string) int {
freq := make(map[rune]int)
for _, c := range text { freq[c]++ }
// "balloon" needs: b×1, a×1, l×2, o×2, n×1
return min(freq['b'], freq['a'], freq['l']/2, freq['o']/2, freq['n'])
}
func min(vals ...int) int {
m := vals[0]
for _, v := range vals[1:] { if v < m { m = v } }
return m
}
func main() {
fmt.Println(isAnagram("anagram", "nagaram")) // true
fmt.Println(isAnagram("rat", "car")) // false
fmt.Println(groupAnagrams([]string{"eat","tea","tan","ate","nat","bat"}))
fmt.Println(largestUniqueNumber([]int{5, 7, 3, 9, 4, 9, 8, 3, 1})) // 8
fmt.Println(maxNumberOfBalloons("loonbalxballoon")) // 2
}
Use a map to bucket elements by a computed key. Replaces nested loops with a single O(n) pass.
package main
import (
"fmt"
"sort"
)
// ── Top K frequent elements ────────────────────────────────
// Bucket sort: frequency → []elements (O(n), no heap needed)
func topKFrequent(nums []int, k int) []int {
freq := make(map[int]int)
for _, n := range nums { freq[n]++ }
// Bucket: index = frequency, value = list of nums with that freq
buckets := make([][]int, len(nums)+1)
for n, f := range freq { buckets[f] = append(buckets[f], n) }
result := make([]int, 0, k)
for i := len(buckets) - 1; i >= 0 && len(result) < k; i-- {
result = append(result, buckets[i]...)
}
return result[:k]
}
// ── Contiguous array — equal 0s and 1s ────────────────────
// Replace 0→-1, find longest subarray with sum 0
// prefix sum + map: first time we see a prefix sum
func findMaxLength(nums []int) int {
firstSeen := map[int]int{0: -1} // prefixSum → first index
maxLen, sum := 0, 0
for i, n := range nums {
if n == 0 { sum-- } else { sum++ }
if j, ok := firstSeen[sum]; ok {
if l := i - j; l > maxLen { maxLen = l }
} else {
firstSeen[sum] = i
}
}
return maxLen
}
// ── Subarray sum equals K (prefix sum + map) ──────────────
func subarraySum(nums []int, k int) int {
count, sum := 0, 0
freq := map[int]int{0: 1}
for _, n := range nums {
sum += n
count += freq[sum-k]
freq[sum]++
}
return count
}
// ── Longest substring with at most K distinct chars ───────
// Sliding window + frequency map
func lengthOfLongestSubstringKDistinct(s string, k int) int {
freq := make(map[byte]int)
l, maxLen := 0, 0
for r := 0; r < len(s); r++ {
freq[s[r]]++
for len(freq) > k { // shrink until valid
freq[s[l]]--
if freq[s[l]] == 0 { delete(freq, s[l]) }
l++
}
if w := r - l + 1; w > maxLen { maxLen = w }
}
return maxLen
}
// ── Ransom note ───────────────────────────────────────────
// Can ransomNote be built from magazine letters?
func canConstruct(ransomNote, magazine string) bool {
freq := make(map[rune]int)
for _, c := range magazine { freq[c]++ }
for _, c := range ransomNote {
freq[c]--
if freq[c] < 0 { return false }
}
return true
}
// ── 4Sum count ────────────────────────────────────────────
// Count tuples (i,j,k,l) where A[i]+B[j]+C[k]+D[l]==0
// Split into two halves: hash all A+B sums, count matches in C+D
func fourSumCount(A, B, C, D []int) int {
abSum := make(map[int]int)
for _, a := range A {
for _, b := range B {
abSum[a+b]++
}
}
count := 0
for _, c := range C {
for _, d := range D {
count += abSum[-(c + d)]
}
}
return count
}
// ── Largest number of K-sum pairs ─────────────────────────
func maxOperations(nums []int, k int) int {
freq := make(map[int]int)
for _, n := range nums { freq[n]++ }
ops := 0
for n, c := range freq {
comp := k - n
if comp == n {
ops += c / 2
} else if freq[comp] > 0 {
pairs := c
if freq[comp] < pairs { pairs = freq[comp] }
ops += pairs
freq[comp] = 0
freq[n] = 0
}
}
return ops
}
// ── Jewels and stones ─────────────────────────────────────
func numJewelsInStones(jewels, stones string) int {
set := make(map[rune]bool)
for _, j := range jewels { set[j] = true }
count := 0
for _, s := range stones { if set[s] { count++ } }
return count
}
func main() {
fmt.Println(topKFrequent([]int{1,1,1,2,2,3}, 2)) // [1 2]
fmt.Println(findMaxLength([]int{0,1,0,0,1,1,0})) // 6
fmt.Println(subarraySum([]int{1,1,1}, 2)) // 2
fmt.Println(canConstruct("aa", "aab")) // true
fmt.Println(numJewelsInStones("aA", "aAAbbbb")) // 3
fmt.Println(fourSumCount(
[]int{1,2}, []int{-2,-1}, []int{-1,2}, []int{0,2})) // 2
words := []string{"eat","tea","tan","ate","nat","bat"}
groups := groupAnagramsFromAbove(words)
for _, g := range groups { sort.Strings(g); fmt.Println(g) }
}
// placeholder — already defined above in grouping example
func groupAnagramsFromAbove(words []string) [][]string {
groups := make(map[string][]string)
for _, w := range words {
b := []byte(w); sort.Slice(b, func(i, j int) bool { return b[i] < b[j] })
key := string(b)
groups[key] = append(groups[key], w)
}
result := make([][]string, 0, len(groups))
for _, g := range groups { result = append(result, g) }
return result
}
Prefix sum alone answers range-sum queries. Combined with a hash map it answers "how many subarrays satisfy condition X" in a single pass. The key insight: sum[l..r] = prefix[r] - prefix[l-1]. If we want sum == k, we need prefix[r] - k to have appeared before.
package main
import "fmt"
// ── Template: count subarrays where f(sum) is true ────────
//
// freq := map[int]int{identity: 1} // seed for subarrays starting at 0
// sum, count := 0, 0
// for _, n := range nums {
// sum = transform(sum, n) // update running aggregate
// count += freq[sum - target] // how many earlier prefixes match
// freq[sum]++ // record this prefix
// }
// ── Subarray sum equals K ──────────────────────────────────
func subarraySumK(nums []int, k int) int {
freq := map[int]int{0: 1}
sum, count := 0, 0
for _, n := range nums {
sum += n
count += freq[sum-k]
freq[sum]++
}
return count
}
// ── Subarray sum divisible by K ───────────────────────────
// Two subarrays have same (sum % k) → their difference is divisible by k
func subarraysDivByK(nums []int, k int) int {
freq := map[int]int{0: 1}
sum, count := 0, 0
for _, n := range nums {
sum += n
mod := ((sum % k) + k) % k // handle negative modulo
count += freq[mod]
freq[mod]++
}
return count
}
// ── Longest subarray with sum 0 ───────────────────────────
func longestSubarraySumZero(nums []int) int {
firstSeen := map[int]int{0: -1}
sum, maxLen := 0, 0
for i, n := range nums {
sum += n
if j, ok := firstSeen[sum]; ok {
if l := i - j; l > maxLen { maxLen = l }
} else {
firstSeen[sum] = i // only store FIRST occurrence
}
}
return maxLen
}
// ── Count subarrays with odd sum ─────────────────────────
// Odd sum = prefix sums have different parity at endpoints
func numOfSubarrays(nums []int) int {
const mod = 1_000_000_007
odd, even, sum, count := 0, 1, 0, 0 // even starts at 1 (empty prefix)
for _, n := range nums {
sum += n
if sum%2 == 0 {
count = (count + odd) % mod
even++
} else {
count = (count + even) % mod
odd++
}
}
return count
}
// ── Binary array: longest subarray with equal 0s and 1s ───
// Map 0→-1, find longest subarray with sum 0
func findMaxLengthBinary(nums []int) int {
firstSeen := map[int]int{0: -1}
sum, maxLen := 0, 0
for i, n := range nums {
if n == 0 { sum-- } else { sum++ }
if j, ok := firstSeen[sum]; ok {
if l := i - j; l > maxLen { maxLen = l }
} else {
firstSeen[sum] = i
}
}
return maxLen
}
// ── Path sum equals target (tree, prefix sum + map) ───────
// Extends the 1D pattern to binary trees via DFS
type TreeNode struct{ Val int; Left, Right *TreeNode }
func pathSum(root *TreeNode, target int) int {
freq := map[int]int{0: 1}
var dfs func(node *TreeNode, curr int) int
dfs = func(node *TreeNode, curr int) int {
if node == nil { return 0 }
curr += node.Val
count := freq[curr-target] // subarrays ending here with sum=target
freq[curr]++
count += dfs(node.Left, curr) + dfs(node.Right, curr)
freq[curr]-- // backtrack
return count
}
return dfs(root, 0)
}
func main() {
fmt.Println(subarraySumK([]int{1, 2, 3}, 3)) // 2
fmt.Println(subarraySumK([]int{1, 1, 1}, 2)) // 2
fmt.Println(subarraysDivByK([]int{4,5,0,-2,-3,1}, 5)) // 7
fmt.Println(longestSubarraySumZero([]int{1,-1,3,-3,2})) // 4
fmt.Println(findMaxLengthBinary([]int{0,1,0,0,1,1,0})) // 6
}
package main
import "fmt"
// ── Happy number ──────────────────────────────────────────
// Repeatedly sum squares of digits — happy if reaches 1, else cycles
func isHappy(n int) bool {
seen := make(map[int]bool)
for n != 1 {
if seen[n] { return false }
seen[n] = true
n = sumOfSquares(n)
}
return true
}
func sumOfSquares(n int) int {
sum := 0
for n > 0 { d := n % 10; sum += d * d; n /= 10 }
return sum
}
// Floyd's cycle variant — O(1) space (no set needed)
func isHappyFloyd(n int) bool {
slow, fast := n, sumOfSquares(n)
for fast != 1 && slow != fast {
slow = sumOfSquares(slow)
fast = sumOfSquares(sumOfSquares(fast))
}
return fast == 1
}
// ── Longest consecutive sequence ─────────────────────────
// O(n) using set: only start counting from sequence beginnings
func longestConsecutive(nums []int) int {
set := make(map[int]bool)
for _, n := range nums { set[n] = true }
maxLen := 0
for n := range set {
if !set[n-1] { // n is start of a sequence
length := 1
for set[n+length] { length++ }
if length > maxLen { maxLen = length }
}
}
return maxLen
}
// ── Destination city ──────────────────────────────────────
// Find the city that is never a source (only a destination)
func destCity(paths [][]string) string {
sources := make(map[string]bool)
for _, p := range paths { sources[p[0]] = true }
for _, p := range paths {
if !sources[p[1]] { return p[1] }
}
return ""
}
// ── Unique number of occurrences ──────────────────────────
// True if all occurrence counts are distinct
func uniqueOccurrences(arr []int) bool {
freq := make(map[int]int)
for _, n := range arr { freq[n]++ }
seen := make(map[int]bool)
for _, c := range freq {
if seen[c] { return false }
seen[c] = true
}
return true
}
// ── Intersection of two arrays ────────────────────────────
func intersection(a, b []int) []int {
setA := make(map[int]bool)
for _, v := range a { setA[v] = true }
seen := make(map[int]bool)
var result []int
for _, v := range b {
if setA[v] && !seen[v] {
result = append(result, v)
seen[v] = true
}
}
return result
}
// ── Find duplicate in array of N+1 integers [1..N] ────────
// Without modifying the array, O(1) space: Floyd on array-as-graph
func findDuplicate(nums []int) int {
slow, fast := nums[0], nums[nums[0]]
for slow != fast {
slow = nums[slow]
fast = nums[nums[fast]]
}
slow = 0
for slow != fast {
slow = nums[slow]
fast = nums[fast]
}
return slow
}
func main() {
fmt.Println(isHappy(19)) // true
fmt.Println(isHappyFloyd(19)) // true
fmt.Println(longestConsecutive([]int{100,4,200,1,3,2})) // 4 (1-2-3-4)
fmt.Println(longestConsecutive([]int{0,3,7,2,5,8,4,6,0,1})) // 9
fmt.Println(destCity([][]string{{"B","C"},{"D","B"},{"A","D"}})) // "C"
fmt.Println(uniqueOccurrences([]int{1, 2, 2, 1, 1, 3})) // true
fmt.Println(intersection([]int{1,2,2,1}, []int{2,2})) // [2]
fmt.Println(findDuplicate([]int{1, 3, 4, 2, 2})) // 2
}
for k, v := range m giving the same order twice. If you need sorted output, collect the keys into a slice and sort.Ints(keys) first.
0 for int, false for bool) — no panic, no error. This is useful for frequency maps (freq[x]++ works even on first access) but dangerous when zero is a valid value. Always use the comma-ok idiom: v, ok := m[k] when you need to distinguish "missing" from "zero".
Array / Slice Patterns
A collection of algorithmic building blocks specifically for slice manipulation: prefix sums, Kadane's max subarray, Dutch flag partition, interval merging, product-except-self, rain water trapping, jump games, matrix operations, and in-place index tricks.
A craftsperson's toolkit. Prefix sums are a measuring tape — cut any span instantly. Kadane's is a treasure hunter deciding at each step "do I keep going or start fresh?" Dutch flag is sorting coloured beads by hand in one pass. Each tool solves a specific class of problem elegantly.
Each pattern exploits a specific slice property. Prefix sums trade O(n) space for O(1) query time. Kadane's uses the "extend or reset" DP decision in a single pass. Interval merging relies on sorting by start/end to reduce N² comparisons to N. Cyclic sort exploits value-in-range-[1..n] to use the array as its own index.
Match the problem keywords: "subarray sum" → prefix sum + hashmap. "max contiguous" → Kadane's. "sort 0/1/2" or "partition" → Dutch flag. "merge/insert intervals" → sort by start. "product except self" → left/right pass. "missing/duplicate in [1..n]" → cyclic sort.
These patterns come up in the majority of slice-based interview questions. Master the template first, then the variations will fall into place.
Precompute cumulative sums so any subarray sum [l..r] is answered in O(1). Foundation for many DP and range-query problems.
package main
import "fmt"
// ── 1D Prefix Sum ─────────────────────────────────────────
func buildPrefix(nums []int) []int {
prefix := make([]int, len(nums)+1) // prefix[0] = 0 sentinel
for i, v := range nums {
prefix[i+1] = prefix[i] + v
}
return prefix
}
// Range sum [l, r] inclusive — O(1)
func rangeSum(prefix []int, l, r int) int {
return prefix[r+1] - prefix[l]
}
// ── Subarray Sum Equals K ─────────────────────────────────
// Count subarrays whose sum == k → O(n) with prefix+hashmap
func subarraySum(nums []int, k int) int {
count := 0
sum := 0
freq := map[int]int{0: 1} // sum→count; seed 0 for subarrays from index 0
for _, n := range nums {
sum += n
count += freq[sum-k] // how many times (sum-k) appeared before
freq[sum]++
}
return count
}
// ── Subarray Sum Divisible by K ───────────────────────────
func subarraySumDivK(nums []int, k int) int {
count, sum := 0, 0
rem := map[int]int{0: 1}
for _, n := range nums {
sum += n
mod := ((sum % k) + k) % k // handle negative
count += rem[mod]
rem[mod]++
}
return count
}
// ── 2D Prefix Sum (matrix range queries) ─────────────────
func build2DPrefix(matrix [][]int) [][]int {
m, n := len(matrix), len(matrix[0])
p := make([][]int, m+1)
for i := range p { p[i] = make([]int, n+1) }
for i := 1; i <= m; i++ {
for j := 1; j <= n; j++ {
p[i][j] = matrix[i-1][j-1] + p[i-1][j] + p[i][j-1] - p[i-1][j-1]
}
}
return p
}
// Sum of submatrix (r1,c1) to (r2,c2) inclusive
func matrixRangeSum(p [][]int, r1, c1, r2, c2 int) int {
return p[r2+1][c2+1] - p[r1][c2+1] - p[r2+1][c1] + p[r1][c1]
}
func main() {
nums := []int{1, 2, 3, 4, 5}
pre := buildPrefix(nums)
fmt.Println(rangeSum(pre, 1, 3)) // 2+3+4 = 9
fmt.Println(rangeSum(pre, 0, 4)) // 15
fmt.Println(subarraySum([]int{1, 1, 1}, 2)) // 2
fmt.Println(subarraySum([]int{1, 2, 3, -1, 1, 2}, 3)) // 3
fmt.Println(subarraySumDivK([]int{4, 5, 0, -2, -3, 1}, 5)) // 7
matrix := [][]int{{3,0,1,4,2},{5,6,3,2,1},{1,2,0,1,5},{4,1,0,1,7},{1,0,3,0,5}}
p2 := build2DPrefix(matrix)
fmt.Println(matrixRangeSum(p2, 2, 1, 4, 3)) // 8
}
freq[sum-k] asks: "how many prefixes ended with sum sum-k?" The subarray between that prefix and now sums to exactly k. The seed {0: 1} handles subarrays starting at index 0.
package main
import "fmt"
// Max subarray sum
func maxSubArray(nums []int) int {
maxSum, curr := nums[0], nums[0]
for _, n := range nums[1:] {
if curr < 0 { curr = 0 } // reset: starting fresh is better
curr += n
if curr > maxSum { maxSum = curr }
}
return maxSum
}
// Max subarray sum — with indices (track start/end)
func maxSubArrayIdx(nums []int) (int, int, int) {
maxSum, curr := nums[0], nums[0]
start, end, tempStart := 0, 0, 0
for i := 1; i < len(nums); i++ {
if curr < 0 { curr = 0; tempStart = i }
curr += nums[i]
if curr > maxSum {
maxSum = curr
start, end = tempStart, i
}
}
return maxSum, start, end
}
// Max product subarray (handle negatives with two passes)
func maxProduct(nums []int) int {
maxP, minP, result := nums[0], nums[0], nums[0]
for _, n := range nums[1:] {
if n < 0 { maxP, minP = minP, maxP } // swap: negative flips max/min
if n > maxP*1 { maxP = n } else { maxP = maxP * n } // simplified:
maxP = max(n, maxP*n) // max of: start fresh OR extend
minP = min(n, minP*n) // track min for future negative flip
if maxP > result { result = maxP }
}
return result
}
// Cleaner max product
func maxProductClean(nums []int) int {
maxP, minP, res := nums[0], nums[0], nums[0]
for _, n := range nums[1:] {
maxP, minP = max(n, max(maxP*n, minP*n)), min(n, min(maxP*n, minP*n))
if maxP > res { res = maxP }
}
return res
}
// Circular subarray max (Kadane + total-minSubarray)
func maxSubarrayCircular(nums []int) int {
// Case 1: normal Kadane
maxSum, minSum := nums[0], nums[0]
currMax, currMin, total := nums[0], nums[0], nums[0]
for _, n := range nums[1:] {
total += n
currMax = max(n, currMax+n)
currMin = min(n, currMin+n)
if currMax > maxSum { maxSum = currMax }
if currMin < minSum { minSum = currMin }
}
// Case 2: wrap-around = total - minSubarray
// Edge: if all negative, wrap would give 0 (empty), return maxSum
if maxSum < 0 { return maxSum }
if wrap := total - minSum; wrap > maxSum { return wrap }
return maxSum
}
func max(a, b int) int { if a > b { return a }; return b }
func min(a, b int) int { if a < b { return a }; return b }
func main() {
fmt.Println(maxSubArray([]int{-2, 1, -3, 4, -1, 2, 1, -5, 4})) // 6
sum, s, e := maxSubArrayIdx([]int{-2, 1, -3, 4, -1, 2, 1, -5, 4})
fmt.Printf("sum=%d idx=[%d,%d]\n", sum, s, e) // sum=6 idx=[3,6]
fmt.Println(maxProductClean([]int{2, 3, -2, 4})) // 6
fmt.Println(maxSubarrayCircular([]int{1, -2, 3, -2})) // 3
fmt.Println(maxSubarrayCircular([]int{5, -3, 5})) // 10 (wrap)
}
maxSum = nums[0] (not 0) and start iterating from index 1.
Partition a slice into three regions: <pivot | ==pivot | >pivot. Classic: sort 0/1/2 in one pass.
package main
import "fmt"
// Sort Colors — LeetCode 75
// Invariants: nums[0..lo-1]=0, nums[lo..mid-1]=1, nums[hi+1..n-1]=2
func sortColors(nums []int) {
lo, mid, hi := 0, 0, len(nums)-1
for mid <= hi {
switch nums[mid] {
case 0:
nums[lo], nums[mid] = nums[mid], nums[lo]
lo++; mid++
case 1:
mid++ // already in place
case 2:
nums[mid], nums[hi] = nums[hi], nums[mid]
hi-- // don't mid++ — swapped element not yet examined
}
}
}
// General 3-way partition around pivot value
func threeWayPartition(nums []int, pivot int) []int {
lo, mid, hi := 0, 0, len(nums)-1
for mid <= hi {
switch {
case nums[mid] < pivot:
nums[lo], nums[mid] = nums[mid], nums[lo]
lo++; mid++
case nums[mid] == pivot:
mid++
default:
nums[mid], nums[hi] = nums[hi], nums[mid]
hi--
}
}
return nums
}
// Move Zeros to end (2-way partition, stable)
func moveZeroes(nums []int) {
writeIdx := 0
for _, n := range nums {
if n != 0 { nums[writeIdx] = n; writeIdx++ }
}
for writeIdx < len(nums) { nums[writeIdx] = 0; writeIdx++ }
}
// Partition array: negatives left, non-negatives right (2-way)
func partition(nums []int) []int {
l, r := 0, len(nums)-1
for l < r {
for l < r && nums[l] < 0 { l++ }
for l < r && nums[r] >= 0 { r-- }
if l < r { nums[l], nums[r] = nums[r], nums[l] }
}
return nums
}
func main() {
c := []int{2, 0, 2, 1, 1, 0}
sortColors(c)
fmt.Println(c) // [0 0 1 1 2 2]
nums := []int{3, 1, 4, 1, 5, 9, 2, 6, 5}
fmt.Println(threeWayPartition(nums, 4)) // all <4 | 4s | all >4
z := []int{0, 1, 0, 3, 12}
moveZeroes(z)
fmt.Println(z) // [1 3 12 0 0]
}
package main
import (
"fmt"
"sort"
)
type Interval [2]int
// Merge overlapping intervals
func merge(intervals []Interval) []Interval {
sort.Slice(intervals, func(i, j int) bool {
return intervals[i][0] < intervals[j][0]
})
result := []Interval{intervals[0]}
for _, curr := range intervals[1:] {
last := &result[len(result)-1]
if curr[0] <= last[1] { // overlap
if curr[1] > last[1] { last[1] = curr[1] } // extend
} else {
result = append(result, curr)
}
}
return result
}
// Insert interval into sorted non-overlapping list
func insert(intervals []Interval, newInterval Interval) []Interval {
result := []Interval{}
i := 0
// Add all intervals that end before new one starts
for i < len(intervals) && intervals[i][1] < newInterval[0] {
result = append(result, intervals[i]); i++
}
// Merge all overlapping intervals
for i < len(intervals) && intervals[i][0] <= newInterval[1] {
if intervals[i][0] < newInterval[0] { newInterval[0] = intervals[i][0] }
if intervals[i][1] > newInterval[1] { newInterval[1] = intervals[i][1] }
i++
}
result = append(result, newInterval)
// Add remaining
result = append(result, intervals[i:]...)
return result
}
// Meeting rooms — can attend all? (no overlap)
func canAttendMeetings(intervals []Interval) bool {
sort.Slice(intervals, func(i, j int) bool { return intervals[i][0] < intervals[j][0] })
for i := 1; i < len(intervals); i++ {
if intervals[i][0] < intervals[i-1][1] { return false }
}
return true
}
// Meeting rooms II — min rooms needed (sweep line / min-heap)
func minMeetingRooms(intervals []Interval) int {
if len(intervals) == 0 { return 0 }
starts := make([]int, len(intervals))
ends := make([]int, len(intervals))
for i, iv := range intervals { starts[i], ends[i] = iv[0], iv[1] }
sort.Ints(starts); sort.Ints(ends)
rooms, endPtr := 0, 0
for i := range starts {
if starts[i] < ends[endPtr] {
rooms++ // new room needed
} else {
endPtr++ // reuse a room
}
}
return rooms
}
func main() {
ivs := []Interval{{1,3},{2,6},{8,10},{15,18}}
fmt.Println(merge(ivs)) // [[1 6] [8 10] [15 18]]
ivs2 := []Interval{{1,2},{3,5},{6,7},{8,10},{12,16}}
fmt.Println(insert(ivs2, Interval{4, 8})) // [[1 2] [3 10] [12 16]]
meetings := []Interval{{0,30},{5,10},{15,20}}
fmt.Println(canAttendMeetings(meetings)) // false
fmt.Println(minMeetingRooms(meetings)) // 2
}
package main
import "fmt"
// No division, O(1) extra space (output array doesn't count)
// Left pass: result[i] = product of all elements left of i
// Right pass: multiply in product of all elements right of i
func productExceptSelf(nums []int) []int {
n := len(nums)
result := make([]int, n)
// Left products
result[0] = 1
for i := 1; i < n; i++ {
result[i] = result[i-1] * nums[i-1]
}
// Right products — running right multiplier
right := 1
for i := n - 1; i >= 0; i-- {
result[i] *= right
right *= nums[i]
}
return result
}
// Variant: handle zeros — count zeros, then build answer
func productExceptSelfWithZeros(nums []int) []int {
total, zeros := 1, 0
for _, n := range nums {
if n == 0 { zeros++ } else { total *= n }
}
result := make([]int, len(nums))
for i, n := range nums {
switch zeros {
case 0: result[i] = total / n
case 1:
if n == 0 { result[i] = total }
// zeros >= 2: all stay 0
}
}
return result
}
func main() {
fmt.Println(productExceptSelf([]int{1, 2, 3, 4})) // [24 12 8 6]
fmt.Println(productExceptSelf([]int{-1, 1, 0, -3, 3})) // [0 0 9 0 0]
}
package main
import "fmt"
// Two-pointer approach: O(n) time, O(1) space
// Water at position i = min(maxLeft, maxRight) - height[i]
// Move pointer from the side with smaller max height
func trap(height []int) int {
l, r := 0, len(height)-1
maxL, maxR := 0, 0
water := 0
for l < r {
if height[l] < height[r] {
if height[l] >= maxL { maxL = height[l] } else { water += maxL - height[l] }
l++
} else {
if height[r] >= maxR { maxR = height[r] } else { water += maxR - height[r] }
r--
}
}
return water
}
// Prefix max approach (easier to understand, O(n) space)
func trapWithArrays(height []int) int {
n := len(height)
maxL, maxR := make([]int, n), make([]int, n)
maxL[0] = height[0]
for i := 1; i < n; i++ {
if height[i] > maxL[i-1] { maxL[i] = height[i] } else { maxL[i] = maxL[i-1] }
}
maxR[n-1] = height[n-1]
for i := n - 2; i >= 0; i-- {
if height[i] > maxR[i+1] { maxR[i] = height[i] } else { maxR[i] = maxR[i+1] }
}
water := 0
for i := 0; i < n; i++ {
waterLevel := maxL[i]; if maxR[i] < waterLevel { waterLevel = maxR[i] }
if diff := waterLevel - height[i]; diff > 0 { water += diff }
}
return water
}
func main() {
fmt.Println(trap([]int{0,1,0,2,1,0,1,3,2,1,2,1})) // 6
fmt.Println(trap([]int{4,2,0,3,2,5})) // 9
}
package main
import "fmt"
// Jump Game I — can reach last index?
// Greedy: track the furthest reachable index
func canJump(nums []int) bool {
maxReach := 0
for i, n := range nums {
if i > maxReach { return false } // can't reach index i
if i+n > maxReach { maxReach = i + n }
}
return true
}
// Jump Game II — minimum jumps to reach last index
// Greedy: at each "jump boundary", take the jump that reaches farthest
func jump(nums []int) int {
jumps, currEnd, farthest := 0, 0, 0
for i := 0; i < len(nums)-1; i++ {
if i+nums[i] > farthest { farthest = i + nums[i] }
if i == currEnd { // must jump here
jumps++
currEnd = farthest
}
}
return jumps
}
// Jump Game III — can reach index with value 0?
// BFS from start, jump ±nums[i]
func canReachZero(arr []int, start int) bool {
visited := make([]bool, len(arr))
queue := []int{start}
for len(queue) > 0 {
i := queue[0]; queue = queue[1:]
if arr[i] == 0 { return true }
if visited[i] { continue }
visited[i] = true
if l := i - arr[i]; l >= 0 && !visited[l] { queue = append(queue, l) }
if r := i + arr[i]; r < len(arr) && !visited[r] { queue = append(queue, r) }
}
return false
}
func main() {
fmt.Println(canJump([]int{2, 3, 1, 1, 4})) // true
fmt.Println(canJump([]int{3, 2, 1, 0, 4})) // false
fmt.Println(jump([]int{2, 3, 1, 1, 4})) // 2
fmt.Println(jump([]int{2, 3, 0, 1, 4})) // 2
fmt.Println(canReachZero([]int{4,2,3,0,3,1,2}, 5)) // true
}
package main
import "fmt"
// Rotate NxN matrix 90° clockwise in-place
// Step 1: Transpose (swap [i][j] with [j][i])
// Step 2: Reverse each row
func rotate(matrix [][]int) {
n := len(matrix)
// Transpose
for i := 0; i < n; i++ {
for j := i + 1; j < n; j++ {
matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]
}
}
// Reverse each row
for i := 0; i < n; i++ {
for l, r := 0, n-1; l < r; l, r = l+1, r-1 {
matrix[i][l], matrix[i][r] = matrix[i][r], matrix[i][l]
}
}
}
// 90° counter-clockwise: reverse each row first, then transpose
// Set matrix zeroes — if cell is 0, set its row+col to 0
// O(1) space: use first row/col as flags
func setZeroes(matrix [][]int) {
m, n := len(matrix), len(matrix[0])
firstRowZero, firstColZero := false, false
for j := 0; j < n; j++ { if matrix[0][j] == 0 { firstRowZero = true } }
for i := 0; i < m; i++ { if matrix[i][0] == 0 { firstColZero = true } }
// Use first row/col to mark
for i := 1; i < m; i++ {
for j := 1; j < n; j++ {
if matrix[i][j] == 0 { matrix[i][0] = 0; matrix[0][j] = 0 }
}
}
for i := 1; i < m; i++ { if matrix[i][0] == 0 { for j := 1; j < n; j++ { matrix[i][j] = 0 } } }
for j := 1; j < n; j++ { if matrix[0][j] == 0 { for i := 1; i < m; i++ { matrix[i][j] = 0 } } }
if firstRowZero { for j := 0; j < n; j++ { matrix[0][j] = 0 } }
if firstColZero { for i := 0; i < m; i++ { matrix[i][0] = 0 } }
}
// Spiral order traversal
func spiralOrder(matrix [][]int) []int {
result := []int{}
top, bottom, left, right := 0, len(matrix)-1, 0, len(matrix[0])-1
for top <= bottom && left <= right {
for j := left; j <= right; j++ { result = append(result, matrix[top][j]) }; top++
for i := top; i <= bottom; i++ { result = append(result, matrix[i][right]) }; right--
if top <= bottom {
for j := right; j >= left; j-- { result = append(result, matrix[bottom][j]) }; bottom--
}
if left <= right {
for i := bottom; i >= top; i-- { result = append(result, matrix[i][left]) }; left++
}
}
return result
}
// Search in sorted matrix (each row sorted, first of row > last of prev)
// Treat as flat 1D sorted array — binary search
func searchMatrix(matrix [][]int, target int) bool {
m, n := len(matrix), len(matrix[0])
lo, hi := 0, m*n-1
for lo <= hi {
mid := lo + (hi-lo)/2
val := matrix[mid/n][mid%n]
switch {
case val == target: return true
case val < target: lo = mid + 1
default: hi = mid - 1
}
}
return false
}
func main() {
m := [][]int{{1,2,3},{4,5,6},{7,8,9}}
rotate(m)
fmt.Println(m) // [[7 4 1] [8 5 2] [9 6 3]]
m2 := [][]int{{1,2,3},{4,5,6},{7,8,9}}
fmt.Println(spiralOrder(m2)) // [1 2 3 6 9 8 7 4 5]
m3 := [][]int{{1,3,5,7},{10,11,16,20},{23,30,34,60}}
fmt.Println(searchMatrix(m3, 3)) // true
fmt.Println(searchMatrix(m3, 13)) // false
}
package main
import "fmt"
// Next Permutation (lexicographically next)
// Algorithm: find rightmost ascent, swap with next-larger, reverse suffix
func nextPermutation(nums []int) {
n := len(nums)
// Step 1: find rightmost i where nums[i] < nums[i+1]
i := n - 2
for i >= 0 && nums[i] >= nums[i+1] { i-- }
if i >= 0 {
// Step 2: find rightmost j > i where nums[j] > nums[i]
j := n - 1
for nums[j] <= nums[i] { j-- }
nums[i], nums[j] = nums[j], nums[i]
}
// Step 3: reverse suffix after i
for l, r := i+1, n-1; l < r; l, r = l+1, r-1 {
nums[l], nums[r] = nums[r], nums[l]
}
}
// Cyclic Sort — for arrays with values in range [1..n] or [0..n-1]
// Place each number at its correct index in O(n) time
func cyclicSort(nums []int) {
i := 0
for i < len(nums) {
correct := nums[i] - 1 // expected index for value nums[i] (1-indexed)
if nums[i] != nums[correct] {
nums[i], nums[correct] = nums[correct], nums[i]
} else {
i++
}
}
}
// Find missing number using cyclic sort (values 1..n, one missing)
func findMissingNumber(nums []int) int {
cyclicSort(nums)
for i, n := range nums {
if n != i+1 { return i + 1 }
}
return len(nums) + 1
}
// Find all duplicates (values 1..n, each appears once or twice)
func findDuplicates(nums []int) []int {
cyclicSort(nums)
var result []int
for i, n := range nums {
if n != i+1 { result = append(result, n) }
}
return result
}
// All permutations (backtracking)
func permutations(nums []int) [][]int {
var result [][]int
var backtrack func(start int)
backtrack = func(start int) {
if start == len(nums) {
tmp := make([]int, len(nums))
copy(tmp, nums)
result = append(result, tmp)
return
}
for i := start; i < len(nums); i++ {
nums[start], nums[i] = nums[i], nums[start]
backtrack(start + 1)
nums[start], nums[i] = nums[i], nums[start]
}
}
backtrack(0)
return result
}
func main() {
n := []int{1, 2, 3}
nextPermutation(n); fmt.Println(n) // [1 3 2]
nextPermutation(n); fmt.Println(n) // [2 1 3]
s := []int{3, 2, 1}
nextPermutation(s); fmt.Println(s) // [1 2 3] — wraps around
nums := []int{3, 1, 4, 2, 5}
cyclicSort(nums)
fmt.Println(nums) // [1 2 3 4 5]
fmt.Println(findMissingNumber([]int{3, 0, 1})) // 2 (0-indexed variant)
fmt.Println(findDuplicates([]int{4, 3, 2, 7, 8, 2, 3, 1})) // [2 3]
fmt.Println(permutations([]int{1, 2, 3})) // all 6 permutations
}
package main
import "fmt"
// Boyer-Moore Voting — majority element (appears > n/2 times)
func majorityElement(nums []int) int {
candidate, count := nums[0], 1
for _, n := range nums[1:] {
if count == 0 { candidate = n }
if n == candidate { count++ } else { count-- }
}
return candidate // guaranteed to exist per problem constraint
}
// Majority Element II — all elements > n/3 (at most 2 candidates)
func majorityElementII(nums []int) []int {
c1, c2, cnt1, cnt2 := 0, 1, 0, 0 // c2=1 ensures c1 != c2 initially
for _, n := range nums {
switch {
case n == c1: cnt1++
case n == c2: cnt2++
case cnt1 == 0: c1, cnt1 = n, 1
case cnt2 == 0: c2, cnt2 = n, 1
default: cnt1--; cnt2--
}
}
var result []int
cnt1, cnt2 = 0, 0
for _, n := range nums { if n == c1 { cnt1++ } else if n == c2 { cnt2++ } }
if cnt1 > len(nums)/3 { result = append(result, c1) }
if cnt2 > len(nums)/3 { result = append(result, c2) }
return result
}
// Rotate array right by k positions — reversal trick
func rotateRight(nums []int, k int) {
n := len(nums)
k %= n
reverse := func(l, r int) {
for l < r { nums[l], nums[r] = nums[r], nums[l]; l++; r-- }
}
reverse(0, n-1) // reverse all
reverse(0, k-1) // reverse first k
reverse(k, n-1) // reverse rest
}
// Find duplicate in array [1..n] — Floyd's cycle detection on array
// Treat array as linked list: index → value → index → ...
func findDuplicate(nums []int) int {
slow, fast := nums[0], nums[nums[0]]
for slow != fast {
slow = nums[slow]
fast = nums[nums[fast]]
}
slow = 0
for slow != fast {
slow = nums[slow]
fast = nums[fast]
}
return slow
}
// H-Index — max h such that h papers have >= h citations
func hIndex(citations []int) int {
n := len(citations)
bucket := make([]int, n+1)
for _, c := range citations {
if c >= n { bucket[n]++ } else { bucket[c]++ }
}
total := 0
for h := n; h >= 0; h-- {
total += bucket[h]
if total >= h { return h }
}
return 0
}
// Smallest missing positive integer — O(n) time, O(1) space
// Key: answer is in [1, n+1]; use the array itself as a hash
func firstMissingPositive(nums []int) int {
n := len(nums)
// Place each nums[i] at index nums[i]-1 if in range [1..n]
for i := 0; i < n; {
j := nums[i] - 1
if j >= 0 && j < n && nums[j] != nums[i] {
nums[i], nums[j] = nums[j], nums[i]
} else {
i++
}
}
for i, v := range nums {
if v != i+1 { return i + 1 }
}
return n + 1
}
func main() {
fmt.Println(majorityElement([]int{3,2,3})) // 3
fmt.Println(majorityElement([]int{2,2,1,1,1,2,2})) // 2
fmt.Println(majorityElementII([]int{3,2,3})) // [3]
fmt.Println(majorityElementII([]int{1,1,1,3,3,2,2,2})) // [1 2]
arr := []int{1, 2, 3, 4, 5, 6, 7}
rotateRight(arr, 3)
fmt.Println(arr) // [5 6 7 1 2 3 4]
fmt.Println(findDuplicate([]int{1, 3, 4, 2, 2})) // 2
fmt.Println(findDuplicate([]int{3, 1, 3, 4, 2})) // 3
fmt.Println(hIndex([]int{3, 0, 6, 1, 5})) // 3
fmt.Println(firstMissingPositive([]int{3, 4, -1, 1})) // 2
fmt.Println(firstMissingPositive([]int{1, 2, 0})) // 3
fmt.Println(firstMissingPositive([]int{7, 8, 9, 11})) // 1
}
Binary Search
An algorithm that halves the search space each step by comparing the midpoint to the target. Requires a sorted array or a monotonic predicate. Eliminates half the candidates in O(1) work per iteration.
Opening a dictionary. You open to the middle — if your word comes before that page, discard the right half; after, discard the left half. After just 20 steps you've searched a million-word dictionary.
Maintain invariant: answer is in [lo, hi]. Compute mid = lo + (hi-lo)/2 (avoids int overflow vs (lo+hi)/2). Adjust lo or hi to maintain invariant. Terminate when lo == hi (lower/upper bound) or lo > hi (exact search).
Sorted array (exact search, lower/upper bound). Rotated sorted array. "Find minimum X such that condition(X) is true" → binary search on answer. Peak finding. Any problem where feasibility is monotonic: if X works, X+1 also works (or vice versa).
lo, hi as the active range. Compute mid = lo + (hi-lo)/2 (prevents overflow). Compare nums[mid] to target → narrow to left or right half. Terminates when lo > hi (exact) or lo == hi (boundary).package main
import "fmt"
// Standard — find exact target
func binarySearch(nums []int, target int) int {
l, r := 0, len(nums)-1
for l <= r {
mid := l + (r-l)/2 // avoids overflow vs (l+r)/2
switch {
case nums[mid] == target: return mid
case nums[mid] < target: l = mid + 1
default: r = mid - 1
}
}
return -1
}
// Lower bound — first index where nums[i] >= target
func lowerBound(nums []int, target int) int {
l, r := 0, len(nums)
for l < r {
mid := l + (r-l)/2
if nums[mid] < target { l = mid + 1 } else { r = mid }
}
return l // l == r, insert position
}
// Upper bound — first index where nums[i] > target
func upperBound(nums []int, target int) int {
l, r := 0, len(nums)
for l < r {
mid := l + (r-l)/2
if nums[mid] <= target { l = mid + 1 } else { r = mid }
}
return l
}
// Binary search on answer — common Staff+ pattern
// "Find minimum X such that condition(X) is true"
func bsOnAnswer(lo, hi int, condition func(int) bool) int {
for lo < hi {
mid := lo + (hi-lo)/2
if condition(mid) { hi = mid } else { lo = mid + 1 }
}
return lo
}
// Example: Koko eating bananas — min speed to eat all within h hours
func minEatingSpeed(piles []int, h int) int {
canEat := func(speed int) bool {
hours := 0
for _, p := range piles {
hours += (p + speed - 1) / speed // ceil division
}
return hours <= h
}
maxPile := 0
for _, p := range piles { if p > maxPile { maxPile = p } }
return bsOnAnswer(1, maxPile, canEat)
}
func main() {
nums := []int{1, 3, 5, 7, 9, 11}
fmt.Println(binarySearch(nums, 7)) // 3
fmt.Println(lowerBound(nums, 6)) // 3 (first >= 6)
fmt.Println(upperBound(nums, 7)) // 4 (first > 7)
fmt.Println(minEatingSpeed([]int{3,6,7,11}, 8)) // 4
}
condition(x) predicate that is false for [lo..ans-1] and true for [ans..hi]. Search for the boundary.
Stack & Queue
Stack: LIFO (Last In, First Out) — access only the top. Queue: FIFO (First In, First Out) — add at back, remove from front. Go has no built-in implementations — both are built from slices. A monotonic stack maintains elements in sorted order, enabling O(1) next-greater/smaller queries.
Stack: a stack of plates — you can only add or remove from the top. Undo history in an editor. Queue: a supermarket checkout line — first in, first served. Print queue. Monotonic stack: a bouncer at a club who keeps the line sorted by height — shorter arrivals displace previous guests.
Stack: append to push, reslice [:n-1] to pop — O(1) amortised. Queue: naive reslice is O(n) per dequeue; use a head pointer for O(1) amortised (compact when head passes halfway). Monotonic stack: pop all elements violating the monotone invariant before pushing — each element pushed/popped at most once, so O(n) total.
Stack: DFS, parentheses matching, expression evaluation, undo/redo, call stack simulation. Queue: BFS, task scheduling, sliding window max (deque). Monotonic stack: next greater/smaller element, largest rectangle in histogram, daily temperatures.
push: s = append(s, v)pop: v = s[len(s)-1]; s = s[:len(s)-1]peek: s[len(s)-1]Queue enqueue:
append. Dequeue: track head index to avoid O(n) shifts.package main
import "fmt"
// Stack — LIFO using slice
type Stack[T any] struct{ data []T }
func (s *Stack[T]) Push(v T) { s.data = append(s.data, v) }
func (s *Stack[T]) Pop() (T, bool) {
var zero T
if len(s.data) == 0 { return zero, false }
v := s.data[len(s.data)-1]
s.data = s.data[:len(s.data)-1]
return v, true
}
func (s *Stack[T]) Peek() (T, bool) {
var zero T
if len(s.data) == 0 { return zero, false }
return s.data[len(s.data)-1], true
}
func (s *Stack[T]) Len() int { return len(s.data) }
// Queue — FIFO using slice (amortized O(1) with head tracking)
type Queue[T any] struct{ data []T; head int }
func (q *Queue[T]) Enqueue(v T) { q.data = append(q.data, v) }
func (q *Queue[T]) Dequeue() (T, bool) {
var zero T
if q.head >= len(q.data) { return zero, false }
v := q.data[q.head]; q.head++
if q.head > len(q.data)/2 { // compact to avoid leak
q.data = q.data[q.head:]; q.head = 0
}
return v, true
}
func (q *Queue[T]) Len() int { return len(q.data) - q.head }
// Monotonic stack — next greater element
func nextGreaterElement(nums []int) []int {
n := len(nums)
result := make([]int, n)
for i := range result { result[i] = -1 }
stack := []int{} // stores indices
for i := 0; i < n; i++ {
for len(stack) > 0 && nums[i] > nums[stack[len(stack)-1]] {
idx := stack[len(stack)-1]
stack = stack[:len(stack)-1]
result[idx] = nums[i]
}
stack = append(stack, i)
}
return result
}
// Valid parentheses
func isValid(s string) bool {
stack := []rune{}
pairs := map[rune]rune{')': '(', ']': '[', '}': '{'}
for _, c := range s {
if c == '(' || c == '[' || c == '{' {
stack = append(stack, c)
} else {
if len(stack) == 0 || stack[len(stack)-1] != pairs[c] { return false }
stack = stack[:len(stack)-1]
}
}
return len(stack) == 0
}
func main() {
s := &Stack[int]{}
s.Push(1); s.Push(2); s.Push(3)
v, _ := s.Pop(); fmt.Println(v) // 3
q := &Queue[string]{}
q.Enqueue("a"); q.Enqueue("b")
val, _ := q.Dequeue(); fmt.Println(val) // "a"
fmt.Println(nextGreaterElement([]int{2, 1, 2, 4, 3})) // [4 2 4 -1 -1]
fmt.Println(isValid("()[]{}") ) // true
fmt.Println(isValid("([)]") ) // false
}
Linked List
A chain of nodes, each holding a value and a pointer to the next node. Unlike slices, there is no random access — traversal is sequential from the head. Insertions and deletions are O(1) given a pointer to the location — no shifting needed.
A treasure hunt. Each clue (node) tells you the prize at this location and where to find the next clue. There's no shortcut to the 10th clue — you must follow all 9 preceding clues. Inserting a new clue 5 only requires rewriting clue 4 and clue 5's pointers.
Each node is a heap-allocated struct with a Next *ListNode field. The dummy head sentinel node eliminates nil-head edge cases — return dummy.Next at the end. Floyd's cycle detection uses a slow (1-step) and fast (2-step) pointer; they meet iff a cycle exists.
Interview problems: reverse, find middle, detect cycle, merge sorted lists, remove nth from end. Fast/slow pointer (Floyd's) for cycle detection and finding the middle. Dummy head for any insert/delete/merge operation to avoid nil-handling edge cases at the head.
struct{ Val int; Next *Node }.for node != nil { node = node.Next }. Modification always requires a pointer to the previous node. Use a dummy head sentinel to unify edge cases (empty list, single node, head deletion). Fast + slow pointer detects cycles and finds midpoints.package main
import "fmt"
type ListNode struct {
Val int
Next *ListNode
}
// Helper: build list from slice
func buildList(vals []int) *ListNode {
dummy := &ListNode{}
curr := dummy
for _, v := range vals { curr.Next = &ListNode{Val: v}; curr = curr.Next }
return dummy.Next
}
// Helper: print list
func printList(head *ListNode) {
for head != nil { fmt.Printf("%d ", head.Val); head = head.Next }
fmt.Println()
}
// Reverse linked list — iterative
func reverse(head *ListNode) *ListNode {
var prev *ListNode
curr := head
for curr != nil {
next := curr.Next
curr.Next = prev
prev = curr
curr = next
}
return prev
}
// Floyd's cycle detection
func hasCycle(head *ListNode) bool {
slow, fast := head, head
for fast != nil && fast.Next != nil {
slow = slow.Next
fast = fast.Next.Next
if slow == fast { return true }
}
return false
}
// Find middle (slow/fast pointer)
func middleNode(head *ListNode) *ListNode {
slow, fast := head, head
for fast != nil && fast.Next != nil {
slow = slow.Next
fast = fast.Next.Next
}
return slow // for even length: second middle
}
// Merge two sorted lists
func mergeSorted(l1, l2 *ListNode) *ListNode {
dummy := &ListNode{}
curr := dummy
for l1 != nil && l2 != nil {
if l1.Val <= l2.Val { curr.Next = l1; l1 = l1.Next } else { curr.Next = l2; l2 = l2.Next }
curr = curr.Next
}
if l1 != nil { curr.Next = l1 } else { curr.Next = l2 }
return dummy.Next
}
// Remove nth from end (one pass)
func removeNthFromEnd(head *ListNode, n int) *ListNode {
dummy := &ListNode{Next: head}
fast, slow := dummy, dummy
for i := 0; i <= n; i++ { fast = fast.Next }
for fast != nil { fast = fast.Next; slow = slow.Next }
slow.Next = slow.Next.Next
return dummy.Next
}
func main() {
list := buildList([]int{1, 2, 3, 4, 5})
printList(list)
printList(reverse(list))
list2 := buildList([]int{1, 2, 3, 4, 5})
fmt.Println(middleNode(list2).Val) // 3
l1 := buildList([]int{1, 3, 5})
l2 := buildList([]int{2, 4, 6})
printList(mergeSorted(l1, l2))
list3 := buildList([]int{1,2,3,4,5})
printList(removeNthFromEnd(list3, 2)) // 1 2 3 5
}
dummy := &ListNode{} sentinel node to avoid nil-head edge cases in insert, delete, and merge operations. Return dummy.Next.
Doubly Linked List & LRU Cache
head and tail (dummy). Real nodes live between them. Insert after head: wire 4 pointers. Remove any node: wire its prev and next to each other. No nil checks needed because sentinels always exist.package main
import "fmt"
// ── Node ──────────────────────────────────────────────────
type DNode struct {
Val int
Prev, Next *DNode
}
// ── Doubly Linked List with sentinel head/tail ────────────
// head ↔ [node1] ↔ [node2] ↔ ... ↔ tail
// Sentinels eliminate all nil-pointer edge cases
type DoublyLinkedList struct {
head, tail *DNode
size int
}
func NewDLL() *DoublyLinkedList {
head := &DNode{}
tail := &DNode{}
head.Next = tail
tail.Prev = head
return &DoublyLinkedList{head: head, tail: tail}
}
// insertAfter inserts node n immediately after node at
func (dl *DoublyLinkedList) insertAfter(at, n *DNode) {
n.Prev = at
n.Next = at.Next
at.Next.Prev = n
at.Next = n
dl.size++
}
// remove detaches node n from the list
func (dl *DoublyLinkedList) remove(n *DNode) {
n.Prev.Next = n.Next
n.Next.Prev = n.Prev
n.Prev = nil // help GC
n.Next = nil
dl.size--
}
// AddFront inserts a new node at the front (after head sentinel)
func (dl *DoublyLinkedList) AddFront(val int) *DNode {
n := &DNode{Val: val}
dl.insertAfter(dl.head, n)
return n
}
// AddBack inserts a new node at the back (before tail sentinel)
func (dl *DoublyLinkedList) AddBack(val int) *DNode {
n := &DNode{Val: val}
dl.insertAfter(dl.tail.Prev, n)
return n
}
// RemoveFront removes and returns the front node (nil if empty)
func (dl *DoublyLinkedList) RemoveFront() *DNode {
if dl.size == 0 { return nil }
n := dl.head.Next
dl.remove(n)
return n
}
// RemoveBack removes and returns the back node (nil if empty)
func (dl *DoublyLinkedList) RemoveBack() *DNode {
if dl.size == 0 { return nil }
n := dl.tail.Prev
dl.remove(n)
return n
}
// MoveToFront moves an existing node to the front
func (dl *DoublyLinkedList) MoveToFront(n *DNode) {
dl.remove(n)
dl.size++ // remove decremented, insertAfter will increment
dl.insertAfter(dl.head, n)
}
// Front / Back peek
func (dl *DoublyLinkedList) Front() *DNode {
if dl.size == 0 { return nil }
return dl.head.Next
}
func (dl *DoublyLinkedList) Back() *DNode {
if dl.size == 0 { return nil }
return dl.tail.Prev
}
func (dl *DoublyLinkedList) Len() int { return dl.size }
// Print traverses forward
func (dl *DoublyLinkedList) Print() {
for n := dl.head.Next; n != dl.tail; n = n.Next {
fmt.Printf("%d ", n.Val)
}
fmt.Println()
}
// PrintReverse traverses backward
func (dl *DoublyLinkedList) PrintReverse() {
for n := dl.tail.Prev; n != dl.head; n = n.Prev {
fmt.Printf("%d ", n.Val)
}
fmt.Println()
}
func dllDemo() {
dl := NewDLL()
dl.AddBack(1)
dl.AddBack(2)
dl.AddBack(3)
n := dl.AddFront(0)
dl.Print() // 0 1 2 3
dl.PrintReverse() // 3 2 1 0
dl.MoveToFront(dl.Back()) // move 3 to front
dl.Print() // 3 0 1 2
dl.remove(n) // remove node with val=0
dl.Print() // 3 1 2
fmt.Println("len:", dl.Len()) // 3
fmt.Println("front:", dl.Front().Val) // 3
fmt.Println("back:", dl.Back().Val) // 2
}
The classic Staff+ interview problem. HashMap gives O(1) lookup of the node; DLL gives O(1) move-to-front and eviction of the least recently used (back of list).
package main
import "fmt"
// ── LRU Cache Node ────────────────────────────────────────
type LRUNode struct {
key, val int
prev, next *LRUNode
}
// ── LRU Cache ─────────────────────────────────────────────
type LRUCache struct {
capacity int
cache map[int]*LRUNode // key → node (O(1) lookup)
head *LRUNode // MRU end (most recently used)
tail *LRUNode // LRU end (least recently used)
}
func NewLRUCache(capacity int) *LRUCache {
head := &LRUNode{} // sentinel
tail := &LRUNode{} // sentinel
head.next = tail
tail.prev = head
return &LRUCache{
capacity: capacity,
cache: make(map[int]*LRUNode),
head: head,
tail: tail,
}
}
// insertFront places node right after head sentinel (MRU position)
func (c *LRUCache) insertFront(n *LRUNode) {
n.prev = c.head
n.next = c.head.next
c.head.next.prev = n
c.head.next = n
}
// removeNode detaches node from wherever it is
func (c *LRUCache) removeNode(n *LRUNode) {
n.prev.next = n.next
n.next.prev = n.prev
}
// Get returns the value for key, or -1 if not found.
// Moves accessed node to front (most recently used).
func (c *LRUCache) Get(key int) int {
node, ok := c.cache[key]
if !ok { return -1 }
// Move to front — mark as most recently used
c.removeNode(node)
c.insertFront(node)
return node.val
}
// Put inserts or updates key→val.
// Evicts the LRU entry (back of list) if over capacity.
func (c *LRUCache) Put(key, val int) {
if node, ok := c.cache[key]; ok {
// Update existing — move to front
node.val = val
c.removeNode(node)
c.insertFront(node)
return
}
// Evict LRU if at capacity
if len(c.cache) == c.capacity {
lru := c.tail.prev // node just before tail sentinel
c.removeNode(lru)
delete(c.cache, lru.key)
}
// Insert new node at front
node := &LRUNode{key: key, val: val}
c.insertFront(node)
c.cache[key] = node
}
func lruDemo() {
cache := NewLRUCache(3)
cache.Put(1, 10)
cache.Put(2, 20)
cache.Put(3, 30)
fmt.Println(cache.Get(1)) // 10 — 1 moves to MRU
cache.Put(4, 40) // evicts 2 (LRU)
fmt.Println(cache.Get(2)) // -1 (evicted)
fmt.Println(cache.Get(3)) // 30
fmt.Println(cache.Get(4)) // 40
cache.Put(5, 50) // evicts 1 (now LRU after 3 and 4 accessed)
fmt.Println(cache.Get(1)) // -1 (evicted)
}
LFU evicts the least frequently used key. Tie-break: among equal-frequency keys, evict the least recently used. Uses a map of frequency → DLL and tracks minFreq.
package main
import "fmt"
type LFUNode struct {
key, val, freq int
prev, next *LFUNode
}
// freqList is a DLL for one frequency bucket
type freqList struct {
head, tail *LFUNode
size int
}
func newFreqList() *freqList {
h, t := &LFUNode{}, &LFUNode{}
h.next = t; t.prev = h
return &freqList{head: h, tail: t}
}
func (fl *freqList) addFront(n *LFUNode) {
n.prev = fl.head; n.next = fl.head.next
fl.head.next.prev = n; fl.head.next = n
fl.size++
}
func (fl *freqList) remove(n *LFUNode) {
n.prev.next = n.next; n.next.prev = n.prev
fl.size--
}
func (fl *freqList) removeLast() *LFUNode {
if fl.size == 0 { return nil }
n := fl.tail.prev
fl.remove(n)
return n
}
// ── LFU Cache ─────────────────────────────────────────────
type LFUCache struct {
capacity int
minFreq int
keys map[int]*LFUNode // key → node
freqs map[int]*freqList // freq → DLL of nodes at that freq
}
func NewLFUCache(capacity int) *LFUCache {
return &LFUCache{
capacity: capacity,
keys: make(map[int]*LFUNode),
freqs: make(map[int]*freqList),
}
}
func (c *LFUCache) promote(n *LFUNode) {
oldFreq := n.freq
c.freqs[oldFreq].remove(n)
if c.freqs[oldFreq].size == 0 {
delete(c.freqs, oldFreq)
if c.minFreq == oldFreq { c.minFreq++ }
}
n.freq++
if c.freqs[n.freq] == nil { c.freqs[n.freq] = newFreqList() }
c.freqs[n.freq].addFront(n)
}
func (c *LFUCache) Get(key int) int {
n, ok := c.keys[key]
if !ok { return -1 }
c.promote(n)
return n.val
}
func (c *LFUCache) Put(key, val int) {
if c.capacity == 0 { return }
if n, ok := c.keys[key]; ok {
n.val = val
c.promote(n)
return
}
if len(c.keys) == c.capacity {
evicted := c.freqs[c.minFreq].removeLast()
delete(c.keys, evicted.key)
}
n := &LFUNode{key: key, val: val, freq: 1}
c.keys[key] = n
if c.freqs[1] == nil { c.freqs[1] = newFreqList() }
c.freqs[1].addFront(n)
c.minFreq = 1
}
func lfuDemo() {
cache := NewLFUCache(2)
cache.Put(1, 1)
cache.Put(2, 2)
fmt.Println(cache.Get(1)) // 1 (freq[1]=2, freq[2]=1→ 2 is LFU)
cache.Put(3, 3) // evict key 2 (freq=1, LRU among freq-1)
fmt.Println(cache.Get(2)) // -1 (evicted)
fmt.Println(cache.Get(3)) // 3
cache.Put(4, 4) // evict key 3 (freq=1) — key 1 has freq=2
fmt.Println(cache.Get(1)) // 1
fmt.Println(cache.Get(3)) // -1 (evicted)
fmt.Println(cache.Get(4)) // 4
}
func main() {
dllDemo()
fmt.Println("---")
lruDemo()
fmt.Println("---")
lfuDemo()
}
delete(c.cache, lru.key) — remove it from the map and the list. Missing either half corrupts the cache silently: the map returns a stale pointer to a detached node.
prev and next, making all 4-pointer rewires unconditional.
sync.RWMutex (RLock for Get, Lock for Put). (2) Add TTL expiry → store expiry time in the node, check on Get. (3) Upgrade to LFU → the variant above. (4) Distributed LRU → consistent hashing across nodes, local LRU per shard.
Trees
A hierarchical, acyclic graph where every node has exactly one parent (except the root). A binary tree has at most 2 children per node. A BST adds the invariant: left subtree < node < right subtree, enabling O(log n) search on balanced trees.
A company org chart: CEO at root, managers below, employees as leaves. Each employee has exactly one boss but can have multiple direct reports. The BST is a filing system — labels are alphabetically ordered left-to-right, so you never need to search all drawers.
Trees are recursively defined — left/right subtrees are themselves trees. DFS traversals (in/pre/post-order) use the call stack implicitly. BFS uses a queue. Height = O(log n) for balanced, O(n) for skewed. Iterative traversal avoids stack overflow risk on degenerate inputs.
Tree problems almost always use recursion. Identify the return type pattern: bool (path exists), int (aggregate up the tree), void+closure (collect results). LCA: if both targets in different subtrees, current node is ancestor. Iterative inorder for BST validation/sorted output.
package main
import "fmt"
type TreeNode struct {
Val int
Left *TreeNode
Right *TreeNode
}
// ── DFS traversals ─────────────────────────────────────────
func inorder(root *TreeNode) []int {
if root == nil { return nil }
res := inorder(root.Left)
res = append(res, root.Val)
res = append(res, inorder(root.Right)...)
return res
}
// Iterative inorder (common interview ask)
func inorderIter(root *TreeNode) []int {
var res []int
var stack []*TreeNode
curr := root
for curr != nil || len(stack) > 0 {
for curr != nil { stack = append(stack, curr); curr = curr.Left }
curr = stack[len(stack)-1]; stack = stack[:len(stack)-1]
res = append(res, curr.Val)
curr = curr.Right
}
return res
}
// ── BFS / Level-order ──────────────────────────────────────
func levelOrder(root *TreeNode) [][]int {
if root == nil { return nil }
var result [][]int
queue := []*TreeNode{root}
for len(queue) > 0 {
level := make([]int, 0, len(queue))
size := len(queue)
for i := 0; i < size; i++ {
node := queue[i]
level = append(level, node.Val)
if node.Left != nil { queue = append(queue, node.Left) }
if node.Right != nil { queue = append(queue, node.Right) }
}
queue = queue[size:]
result = append(result, level)
}
return result
}
// ── Max depth ──────────────────────────────────────────────
func maxDepth(root *TreeNode) int {
if root == nil { return 0 }
l, r := maxDepth(root.Left), maxDepth(root.Right)
if l > r { return l + 1 }; return r + 1
}
// ── Diameter of binary tree ────────────────────────────────
func diameterOfBinaryTree(root *TreeNode) int {
maxD := 0
var depth func(*TreeNode) int
depth = func(node *TreeNode) int {
if node == nil { return 0 }
l, r := depth(node.Left), depth(node.Right)
if l+r > maxD { maxD = l + r }
if l > r { return l + 1 }; return r + 1
}
depth(root)
return maxD
}
// ── BST operations ─────────────────────────────────────────
func searchBST(root *TreeNode, val int) *TreeNode {
for root != nil {
switch {
case val == root.Val: return root
case val < root.Val: root = root.Left
default: root = root.Right
}
}
return nil
}
func isValidBST(root *TreeNode) bool {
var validate func(*TreeNode, int, int) bool
validate = func(node *TreeNode, min, max int) bool {
if node == nil { return true }
if node.Val <= min || node.Val >= max { return false }
return validate(node.Left, min, node.Val) && validate(node.Right, node.Val, max)
}
return validate(root, -1<<62, 1<<62)
}
// ── Lowest Common Ancestor ─────────────────────────────────
func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
if root == nil || root == p || root == q { return root }
left := lowestCommonAncestor(root.Left, p, q)
right := lowestCommonAncestor(root.Right, p, q)
if left != nil && right != nil { return root }
if left != nil { return left }; return right
}
func main() {
// 4
// / \
// 2 6
// / \ / \
// 1 3 5 7
root := &TreeNode{Val: 4,
Left: &TreeNode{Val: 2,
Left: &TreeNode{Val: 1}, Right: &TreeNode{Val: 3}},
Right: &TreeNode{Val: 6,
Left: &TreeNode{Val: 5}, Right: &TreeNode{Val: 7}},
}
fmt.Println(inorder(root)) // [1 2 3 4 5 6 7]
fmt.Println(inorderIter(root)) // [1 2 3 4 5 6 7]
fmt.Println(levelOrder(root)) // [[4] [2 6] [1 3 5 7]]
fmt.Println(maxDepth(root)) // 3
fmt.Println(isValidBST(root)) // true
}
Graphs
A set of nodes (vertices) connected by edges. Can be directed/undirected, weighted/unweighted, cyclic/acyclic (DAG). The most general data structure — trees, linked lists, and grids are all special cases of graphs.
A road map: cities are nodes, roads are edges. One-way streets are directed edges. Toll costs are weights. "Can I get from A to B?" is reachability. "What's the fastest route?" is shortest path. "Are all cities connected?" is connected components.
Adjacency list ([][]int): O(V+E) space, fast neighbour iteration — prefer for sparse graphs. Adjacency matrix ([][]bool): O(V²) space, O(1) edge lookup — prefer for dense graphs or when edge existence query dominates. DFS uses a visited set; BFS uses a queue. Topological sort only on DAGs.
Any "connection", "path", "dependency", "network", or "spreading" problem. DFS for connectivity, cycles, topological order. BFS for shortest path (unweighted). Dijkstra for weighted shortest path (non-negative). Union-Find for dynamic connectivity. Topological sort for dependency ordering.
[][]int) or adjacency matrix ([][]bool).visited[] to avoid cycles. DFS with a recursion stack detects back-edges (cycles). BFS guarantees shortest path on unweighted graphs. Kahn's algorithm (in-degree queue) gives topological order for DAGs.package main
import "fmt"
type Graph struct {
adj map[int][]int
}
func newGraph() *Graph { return &Graph{adj: make(map[int][]int)} }
func (g *Graph) AddEdge(u, v int) { g.adj[u] = append(g.adj[u], v) } // directed
// DFS — recursive
func (g *Graph) DFS(start int) []int {
visited := make(map[int]bool)
var result []int
var dfs func(int)
dfs = func(v int) {
if visited[v] { return }
visited[v] = true
result = append(result, v)
for _, n := range g.adj[v] { dfs(n) }
}
dfs(start)
return result
}
// BFS
func (g *Graph) BFS(start int) []int {
visited := map[int]bool{start: true}
queue := []int{start}
var result []int
for len(queue) > 0 {
v := queue[0]; queue = queue[1:]
result = append(result, v)
for _, n := range g.adj[v] {
if !visited[n] { visited[n] = true; queue = append(queue, n) }
}
}
return result
}
// Topological sort (Kahn's BFS — for DAG)
func topoSort(numNodes int, edges [][2]int) []int {
inDegree := make([]int, numNodes)
adj := make([][]int, numNodes)
for _, e := range edges {
adj[e[0]] = append(adj[e[0]], e[1])
inDegree[e[1]]++
}
queue := []int{}
for i := 0; i < numNodes; i++ { if inDegree[i] == 0 { queue = append(queue, i) } }
var result []int
for len(queue) > 0 {
v := queue[0]; queue = queue[1:]
result = append(result, v)
for _, n := range adj[v] {
inDegree[n]--
if inDegree[n] == 0 { queue = append(queue, n) }
}
}
if len(result) != numNodes { return nil } // cycle detected
return result
}
// Cycle detection (directed graph — DFS with color)
// 0=unvisited, 1=in-stack, 2=done
func hasCycle(numNodes int, adj [][]int) bool {
color := make([]int, numNodes)
var dfs func(int) bool
dfs = func(v int) bool {
color[v] = 1
for _, n := range adj[v] {
if color[n] == 1 { return true } // back edge
if color[n] == 0 && dfs(n) { return true }
}
color[v] = 2
return false
}
for i := 0; i < numNodes; i++ { if color[i] == 0 && dfs(i) { return true } }
return false
}
// Number of islands (grid BFS — very common)
func numIslands(grid [][]byte) int {
if len(grid) == 0 { return 0 }
rows, cols := len(grid), len(grid[0])
count := 0
var bfs func(int, int)
bfs = func(r, c int) {
if r < 0 || r >= rows || c < 0 || c >= cols || grid[r][c] != '1' { return }
grid[r][c] = '0' // mark visited
bfs(r+1, c); bfs(r-1, c); bfs(r, c+1); bfs(r, c-1)
}
for r := 0; r < rows; r++ {
for c := 0; c < cols; c++ {
if grid[r][c] == '1' { count++; bfs(r, c) }
}
}
return count
}
func main() {
g := newGraph()
g.AddEdge(0,1); g.AddEdge(0,2); g.AddEdge(1,3); g.AddEdge(2,3)
fmt.Println("DFS:", g.DFS(0)) // [0 1 3 2]
fmt.Println("BFS:", g.BFS(0)) // [0 1 2 3]
edges := [][2]int{{5,2},{5,0},{4,0},{4,1},{2,3},{3,1}}
fmt.Println("Topo:", topoSort(6, edges)) // valid topo order
grid := [][]byte{
{'1','1','0','0'},
{'1','1','0','1'},
{'0','0','0','1'},
}
fmt.Println("Islands:", numIslands(grid)) // 2
}
Heap / Priority Queue
A complete binary tree stored as an array where every parent is ≤ its children (min-heap) or ≥ its children (max-heap). Provides O(1) access to the extreme element and O(log n) insert/remove. Go's container/heap requires implementing a 5-method interface.
A hospital emergency room triage queue. The most critical patient (max-priority) is always treated first, regardless of arrival order. When a new patient arrives or one is discharged, the queue reshuffles instantly to keep the most critical at the front.
Stored as an array. Parent of index i: (i-1)/2. Children of i: 2i+1, 2i+2. Push: append + sift-up (swap with parent while violating heap property). Pop: swap root with last element, shrink, sift-down. Build-heap from unsorted array: O(n) using bottom-up heapify (not N×push).
K largest/smallest elements (min-heap of size k). Median of stream (two heaps: maxHeap for lower half, minHeap for upper half). Dijkstra's algorithm. Merge K sorted lists. Task scheduling by priority. Whenever you need repeated access to the current minimum or maximum.
container/heap — you implement 5 interface methods and it does the rest.i = (i-1)/2, children = 2i+1, 2i+2. Push: append + bubble up. Pop: swap root with last, remove last, bubble down. Go's heap.Push/Pop call your Swap/Less methods. Flip Less to switch min↔max.package main
import (
"container/heap"
"fmt"
)
// ── Min-Heap (container/heap interface) ───────────────────
type MinHeap []int
func (h MinHeap) Len() int { return len(h) }
func (h MinHeap) Less(i, j int) bool { return h[i] < h[j] } // < = min, > = max
func (h MinHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *MinHeap) Push(x any) { *h = append(*h, x.(int)) }
func (h *MinHeap) Pop() any {
old := *h; n := len(old)
x := old[n-1]; *h = old[:n-1]; return x
}
// ── Max-Heap — just flip Less ─────────────────────────────
type MaxHeap []int
func (h MaxHeap) Len() int { return len(h) }
func (h MaxHeap) Less(i, j int) bool { return h[i] > h[j] } // flipped
func (h MaxHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *MaxHeap) Push(x any) { *h = append(*h, x.(int)) }
func (h *MaxHeap) Pop() any { old := *h; n := len(old); x := old[n-1]; *h = old[:n-1]; return x }
// ── Struct heap (priority queue with custom ordering) ─────
type Item struct { value string; priority int }
type PriorityQueue []*Item
func (pq PriorityQueue) Len() int { return len(pq) }
func (pq PriorityQueue) Less(i, j int) bool { return pq[i].priority > pq[j].priority } // max-priority
func (pq PriorityQueue) Swap(i, j int) { pq[i], pq[j] = pq[j], pq[i] }
func (pq *PriorityQueue) Push(x any) { *pq = append(*pq, x.(*Item)) }
func (pq *PriorityQueue) Pop() any {
old := *pq; n := len(old); x := old[n-1]; *pq = old[:n-1]; return x
}
// ── Kth largest element ───────────────────────────────────
// Min-heap of size k — O(n log k)
func findKthLargest(nums []int, k int) int {
h := &MinHeap{}
heap.Init(h)
for _, n := range nums {
heap.Push(h, n)
if h.Len() > k { heap.Pop(h) } // remove smallest
}
return (*h)[0] // min of heap = kth largest
}
func main() {
h := &MinHeap{3, 1, 4, 1, 5, 9}
heap.Init(h)
heap.Push(h, 2)
fmt.Println(heap.Pop(h)) // 1 (min)
fmt.Println(heap.Pop(h)) // 1
pq := &PriorityQueue{
{value: "low", priority: 1},
{value: "high", priority: 10},
{value: "med", priority: 5},
}
heap.Init(pq)
fmt.Println(heap.Pop(pq).(*Item).value) // "high"
fmt.Println(findKthLargest([]int{3,2,1,5,6,4}, 2)) // 5
fmt.Println(findKthLargest([]int{3,2,3,1,2,4,5,5,6}, 4)) // 4
}
Dynamic Programming
An optimisation technique for problems with overlapping subproblems and optimal substructure. Solve each subproblem once and cache the result. Avoids the exponential blowup of naive recursion by trading space for time.
A spreadsheet with formulas. Each cell is a subproblem; it references earlier cells (subproblem results) rather than recomputing from scratch. Fill in cells in the right order and the final answer appears in the last cell — that's bottom-up DP.
Top-down: recursion + memoisation map — natural to write, easy to reason about. Bottom-up: fill a dp table iteratively in dependency order — avoids recursion overhead, easier to optimise space. State = minimal set of variables that uniquely defines a subproblem. Transition = how to get dp[i] from smaller states.
Keywords: "count ways", "minimum cost", "maximum value", "can you achieve", "how many distinct" over sequences, grids, or strings. Confirm overlapping subproblems (draw the recursion tree — do branches repeat?). If yes, DP. If subproblems don't overlap, greedy or divide-and-conquer.
memo map (memoization). Bottom-up: fill a dp[] table from base cases up (tabulation). Both give same complexity. Bottom-up is usually faster (no call stack). Optimise space: if dp[i] only needs dp[i-1], use two variables.package main
import "fmt"
// ── Memoization template (top-down) ──────────────────────
func fib(n int) int {
memo := make(map[int]int)
var dp func(int) int
dp = func(n int) int {
if n <= 1 { return n }
if v, ok := memo[n]; ok { return v }
memo[n] = dp(n-1) + dp(n-2)
return memo[n]
}
return dp(n)
}
// ── Climbing stairs (bottom-up) ───────────────────────────
func climbStairs(n int) int {
if n <= 2 { return n }
prev, curr := 1, 2
for i := 3; i <= n; i++ { prev, curr = curr, prev+curr }
return curr
}
// ── 0/1 Knapsack ──────────────────────────────────────────
func knapsack(weights, values []int, capacity int) int {
n := len(weights)
dp := make([][]int, n+1)
for i := range dp { dp[i] = make([]int, capacity+1) }
for i := 1; i <= n; i++ {
for w := 0; w <= capacity; w++ {
dp[i][w] = dp[i-1][w] // don't take item
if weights[i-1] <= w {
take := dp[i-1][w-weights[i-1]] + values[i-1]
if take > dp[i][w] { dp[i][w] = take }
}
}
}
return dp[n][capacity]
}
// ── Longest Common Subsequence ────────────────────────────
func lcs(s1, s2 string) int {
m, n := len(s1), len(s2)
dp := make([][]int, m+1)
for i := range dp { dp[i] = make([]int, n+1) }
for i := 1; i <= m; i++ {
for j := 1; j <= n; j++ {
if s1[i-1] == s2[j-1] { dp[i][j] = dp[i-1][j-1] + 1 } else
if dp[i-1][j] > dp[i][j-1] { dp[i][j] = dp[i-1][j] } else { dp[i][j] = dp[i][j-1] }
}
}
return dp[m][n]
}
// ── Longest Increasing Subsequence — O(n log n) ───────────
func lengthOfLIS(nums []int) int {
tails := []int{} // tails[i] = smallest tail of LIS with length i+1
for _, n := range nums {
// binary search for insertion point
lo, hi := 0, len(tails)
for lo < hi { mid := lo + (hi-lo)/2; if tails[mid] < n { lo = mid+1 } else { hi = mid } }
if lo == len(tails) { tails = append(tails, n) } else { tails[lo] = n }
}
return len(tails)
}
// ── Coin Change ───────────────────────────────────────────
func coinChange(coins []int, amount int) int {
dp := make([]int, amount+1)
for i := 1; i <= amount; i++ { dp[i] = amount + 1 } // "infinity"
for i := 1; i <= amount; i++ {
for _, c := range coins {
if c <= i && dp[i-c]+1 < dp[i] { dp[i] = dp[i-c] + 1 }
}
}
if dp[amount] > amount { return -1 }; return dp[amount]
}
// ── Word Break ────────────────────────────────────────────
func wordBreak(s string, wordDict []string) bool {
words := make(map[string]bool)
for _, w := range wordDict { words[w] = true }
n := len(s)
dp := make([]bool, n+1)
dp[0] = true
for i := 1; i <= n; i++ {
for j := 0; j < i; j++ {
if dp[j] && words[s[j:i]] { dp[i] = true; break }
}
}
return dp[n]
}
func main() {
fmt.Println(fib(10)) // 55
fmt.Println(climbStairs(10)) // 89
fmt.Println(knapsack([]int{2,3,4,5}, []int{3,4,5,6}, 5)) // 7
fmt.Println(lcs("abcde", "ace")) // 3
fmt.Println(lengthOfLIS([]int{10,9,2,5,3,7,101,18})) // 4
fmt.Println(coinChange([]int{1,5,10,25}, 41)) // 4 (25+10+5+1)
fmt.Println(wordBreak("leetcode", []string{"leet","code"})) // true
}
Depth-First Search
A graph/tree traversal that explores as deep as possible along each branch before backtracking. Uses the call stack (recursive) or an explicit stack (iterative). Visits every reachable node exactly once — O(V+E).
Exploring a cave system with one rule: always take the first unexplored tunnel, mark it, and only backtrack when you hit a dead end. You'll eventually map the entire cave, even the deepest passages, before finishing any "side branch".
Recursive DFS uses the goroutine's call stack as its own stack — simple but risks stack overflow on deep inputs. Iterative DFS pushes neighbours onto an explicit []int stack — visit order can differ from recursive. The key: mark visited before recursing to avoid revisiting in cyclic graphs.
Path existence, all paths, cycle detection, topological sort, connected components, tree traversals, subtree aggregation. Choose DFS over BFS when depth matters more than distance, or when you need to enumerate all solutions (backtracking).
DFS explores as deep as possible before backtracking. The key decision: recursive (implicit call stack) vs iterative (explicit stack). Both are O(V+E) for graphs.
| Use DFS when | Prefer BFS when |
|---|---|
| Path existence, all paths, cycles | Shortest path (unweighted) |
| Topological sort, connected components | Minimum steps/hops |
| Tree traversals, subtree problems | Level-order, layer-by-layer |
| Backtracking / exhaustive search | Multi-source spreading (rotting oranges) |
package main
import "fmt"
// ── Template 1: Recursive DFS on graph ───────────────────
func dfsRecursive(graph [][]int, node int, visited []bool) {
visited[node] = true
fmt.Println("visit:", node)
for _, nei := range graph[node] {
if !visited[nei] {
dfsRecursive(graph, nei, visited)
}
}
}
// ── Template 2: Iterative DFS (explicit stack) ────────────
// NOTE: iterative DFS visits in REVERSE order vs recursive
func dfsIterative(graph [][]int, start int) []int {
n := len(graph)
visited := make([]bool, n)
stack := []int{start}
var order []int
for len(stack) > 0 {
node := stack[len(stack)-1]
stack = stack[:len(stack)-1]
if visited[node] { continue }
visited[node] = true
order = append(order, node)
for _, nei := range graph[node] { // push all neighbours
if !visited[nei] { stack = append(stack, nei) }
}
}
return order
}
// ── Template 3: DFS returning a value (path sum) ─────────
type TreeNode struct{ Val int; Left, Right *TreeNode }
func hasPathSum(root *TreeNode, target int) bool {
if root == nil { return false }
target -= root.Val
if root.Left == nil && root.Right == nil { return target == 0 } // leaf
return hasPathSum(root.Left, target) || hasPathSum(root.Right, target)
}
// ── Template 4: DFS collecting all paths ─────────────────
func allPaths(root *TreeNode, target int) [][]int {
var result [][]int
var dfs func(node *TreeNode, remaining int, path []int)
dfs = func(node *TreeNode, remaining int, path []int) {
if node == nil { return }
path = append(path, node.Val) // path is a copy here (append may allocate)
remaining -= node.Val
if node.Left == nil && node.Right == nil && remaining == 0 {
tmp := make([]int, len(path))
copy(tmp, path) // MUST copy — path slice will be reused
result = append(result, tmp)
return
}
dfs(node.Left, remaining, path)
dfs(node.Right, remaining, path)
}
dfs(root, target, []int{})
return result
}
// ── Template 5: DFS with state / global max ───────────────
func maxPathSum(root *TreeNode) int {
maxSum := -1 << 62
var dfs func(*TreeNode) int
dfs = func(node *TreeNode) int {
if node == nil { return 0 }
left := max(0, dfs(node.Left)) // ignore negative subtrees
right := max(0, dfs(node.Right))
if path := node.Val + left + right; path > maxSum { maxSum = path }
if left > right { return node.Val + left }; return node.Val + right
}
dfs(root)
return maxSum
}
// ── Connected components count ────────────────────────────
func countComponents(n int, edges [][2]int) int {
adj := make([][]int, n)
for _, e := range edges {
adj[e[0]] = append(adj[e[0]], e[1])
adj[e[1]] = append(adj[e[1]], e[0])
}
visited := make([]bool, n)
var dfs func(int)
dfs = func(v int) {
visited[v] = true
for _, nei := range adj[v] { if !visited[nei] { dfs(nei) } }
}
count := 0
for i := 0; i < n; i++ {
if !visited[i] { dfs(i); count++ }
}
return count
}
func max(a, b int) int { if a > b { return a }; return b }
func main() {
graph := [][]int{{1,2},{0,3},{0,3},{1,2}}
visited := make([]bool, 4)
dfsRecursive(graph, 0, visited)
fmt.Println(dfsIterative(graph, 0))
root := &TreeNode{5,
&TreeNode{4, &TreeNode{11, &TreeNode{7,nil,nil}, &TreeNode{2,nil,nil}}, nil},
&TreeNode{8, &TreeNode{13,nil,nil}, &TreeNode{4,nil,&TreeNode{1,nil,nil}}}}
fmt.Println(hasPathSum(root, 22)) // true (5→4→11→2)
fmt.Println(allPaths(root, 22)) // [[5 4 11 2]]
fmt.Println(maxPathSum(root)) // 48
fmt.Println(countComponents(5, [][2]int{{0,1},{1,2},{3,4}})) // 2
}
copy(tmp, path) before appending to results. The path slice's underlying array is mutated as DFS unwinds. Without copy, all results point to the same backing array and end up identical.
||. (2) int — aggregate value up the tree (height, max sum). (3) void with closure — collect results into an outer slice. Recognising which pattern fits the problem is the interview skill.
Breadth-First Search
A traversal that explores level by level, visiting all nodes at distance d before any node at distance d+1. Uses a queue. Guarantees shortest path in unweighted graphs — the first time BFS reaches a node, it has found the minimum number of hops.
Ripples expanding from a stone dropped in a pond. All points at radius 1 are hit first, then radius 2, then 3. The first time the ripple reaches your boat is the shortest-distance path from the stone — BFS is exactly this wave expansion.
A queue holds nodes to visit. For each dequeued node, enqueue all unvisited neighbours and mark them visited immediately on enqueue (not dequeue) to prevent duplicates. Level boundaries are tracked by snapshotting size := len(queue) before each level loop.
Shortest path in unweighted graphs/grids. Minimum steps/transformations (Word Ladder). Level-order tree traversal. Multi-source spreading (Rotting Oranges, 01 Matrix). Any problem where "closest" or "minimum hops" is the goal.
size := len(queue), dequeue exactly size nodes (one level). For each, enqueue unvisited neighbours. Increment level counter after each level batch.BFS explores level by level. Guarantees shortest path in unweighted graphs. Always uses a queue. Go has no built-in queue — use a slice with a head pointer or just reslice.
package main
import "fmt"
// ── Template 1: Basic BFS — shortest path ─────────────────
func bfsShortestPath(graph [][]int, start, end int) int {
visited := make([]bool, len(graph))
visited[start] = true
queue := []int{start}
dist := 0
for len(queue) > 0 {
size := len(queue) // process entire level
for i := 0; i < size; i++ {
node := queue[i]
if node == end { return dist }
for _, nei := range graph[node] {
if !visited[nei] {
visited[nei] = true
queue = append(queue, nei)
}
}
}
queue = queue[size:] // dequeue whole level
dist++
}
return -1 // unreachable
}
// ── Template 2: BFS with distance map ─────────────────────
func bfsDistances(graph [][]int, start int) []int {
dist := make([]int, len(graph))
for i := range dist { dist[i] = -1 }
dist[start] = 0
queue := []int{start}
for len(queue) > 0 {
node := queue[0]; queue = queue[1:]
for _, nei := range graph[node] {
if dist[nei] == -1 {
dist[nei] = dist[node] + 1
queue = append(queue, nei)
}
}
}
return dist
}
// ── Template 3: Multi-source BFS ──────────────────────────
// Start BFS from multiple sources simultaneously
// Classic: 01 Matrix (distance to nearest 0), Rotting Oranges
func multiSourceBFS(grid [][]int) [][]int {
m, n := len(grid), len(grid[0])
dist := make([][]int, m)
for i := range dist { dist[i] = make([]int, n); for j := range dist[i] { dist[i][j] = -1 } }
queue := [][2]int{}
// Seed all sources at distance 0
for i := 0; i < m; i++ {
for j := 0; j < n; j++ {
if grid[i][j] == 0 { dist[i][j] = 0; queue = append(queue, [2]int{i, j}) }
}
}
dirs := [][2]int{{0,1},{0,-1},{1,0},{-1,0}}
for len(queue) > 0 {
cur := queue[0]; queue = queue[1:]
for _, d := range dirs {
r, c := cur[0]+d[0], cur[1]+d[1]
if r >= 0 && r < m && c >= 0 && c < n && dist[r][c] == -1 {
dist[r][c] = dist[cur[0]][cur[1]] + 1
queue = append(queue, [2]int{r, c})
}
}
}
return dist
}
// ── Template 4: BFS on implicit graph (Word Ladder) ────────
// Shortest transformation sequence: "hit" → "cog"
func ladderLength(beginWord, endWord string, wordList []string) int {
wordSet := make(map[string]bool)
for _, w := range wordList { wordSet[w] = true }
if !wordSet[endWord] { return 0 }
queue := []string{beginWord}
visited := map[string]bool{beginWord: true}
steps := 1
for len(queue) > 0 {
size := len(queue)
for i := 0; i < size; i++ {
word := queue[i]
bs := []byte(word)
for pos := 0; pos < len(bs); pos++ {
orig := bs[pos]
for c := byte('a'); c <= byte('z'); c++ {
if c == orig { continue }
bs[pos] = c
next := string(bs)
if next == endWord { return steps + 1 }
if wordSet[next] && !visited[next] {
visited[next] = true
queue = append(queue, next)
}
bs[pos] = orig
}
}
}
queue = queue[size:]
steps++
}
return 0
}
// ── Template 5: 0-1 BFS (deque, edge weight 0 or 1) ───────
// Faster than Dijkstra when edges are only 0 or 1
func zeroOneBFS(graph [][2]int, n, src int, adj [][]struct{ to, w int }) []int {
dist := make([]int, n)
for i := range dist { dist[i] = 1<<62 }
dist[src] = 0
deque := []int{src} // front for w=0, back for w=1
for len(deque) > 0 {
u := deque[0]; deque = deque[1:]
for _, e := range adj[u] {
if nd := dist[u] + e.w; nd < dist[e.to] {
dist[e.to] = nd
if e.w == 0 { deque = append([]int{e.to}, deque...) } else { deque = append(deque, e.to) }
}
}
}
return dist
}
func main() {
graph := [][]int{{1,2},{0,3},{0,3},{1,2,4},{3}}
fmt.Println(bfsShortestPath(graph, 0, 4)) // 2
fmt.Println(bfsDistances(graph, 0)) // [0 1 1 2 3]
grid := [][]int{{0,0,0},{0,1,0},{1,1,1}}
fmt.Println(multiSourceBFS(grid)) // distances from nearest 0
words := []string{"hot","dot","dog","lot","log","cog"}
fmt.Println(ladderLength("hit", "cog", words)) // 5
}
visited[nei] = true right before queue = append(queue, nei).
size := len(queue) before the inner loop, then iterate exactly size times. This processes one complete level per outer iteration, which is how you get the level number (= distance) without storing it in each node.
Backtracking
DFS on a decision tree. At each node you make a choice, recurse to explore consequences, then undo the choice and try the next option. Systematically explores all possibilities while pruning branches that cannot lead to a valid solution.
Solving a maze by hand: walk forward, mark your path, hit a dead end, erase your last step, try a different direction. The "undo" is the backtrack. Good maze-solvers look ahead — they don't take a path that's already obviously blocked (pruning).
Three phases per recursive call: (1) base case — goal reached, collect result. (2) choose — pick the next option, modify state. (3) explore + undo — recurse, then restore state to exactly what it was before the choice. Pruning (validity checks before recursing) is the difference between usable and TLE.
Generate all subsets, combinations, permutations. Constraint satisfaction (N-Queens, Sudoku). Path finding on a grid (Word Search). Any "find all solutions" or "find one valid configuration" problem. If you find yourself writing nested loops of unknown depth — it's backtracking.
make choice → recurse → undo choice. The undo step is what makes it "back"tracking. At each level, loop over valid choices, check constraints before recursing (pruning). Collect results at goal state (leaf of decision tree).Backtracking is DFS on a decision tree. At each node you make a choice, recurse, then undo the choice. The art is pruning branches early to avoid exploring dead ends.
func backtrack(state, choices) { if isGoal(state) { collect(); return }; for each choice { if isValid(choice) { make(choice); backtrack(state, choices); undo(choice) } } }
package main
import (
"fmt"
"sort"
)
// ── Subsets (no duplicates in input) ──────────────────────
func subsets(nums []int) [][]int {
var result [][]int
var backtrack func(start int, current []int)
backtrack = func(start int, current []int) {
tmp := make([]int, len(current))
copy(tmp, current)
result = append(result, tmp) // add at every node, not just leaves
for i := start; i < len(nums); i++ {
current = append(current, nums[i])
backtrack(i+1, current)
current = current[:len(current)-1] // undo
}
}
backtrack(0, []int{})
return result
}
// ── Subsets II (duplicates in input) ──────────────────────
func subsetsWithDup(nums []int) [][]int {
sort.Ints(nums) // sort first to group duplicates
var result [][]int
var backtrack func(start int, current []int)
backtrack = func(start int, current []int) {
tmp := make([]int, len(current)); copy(tmp, current)
result = append(result, tmp)
for i := start; i < len(nums); i++ {
if i > start && nums[i] == nums[i-1] { continue } // skip duplicate
current = append(current, nums[i])
backtrack(i+1, current)
current = current[:len(current)-1]
}
}
backtrack(0, []int{})
return result
}
// ── Combinations: choose k from n ─────────────────────────
func combine(n, k int) [][]int {
var result [][]int
var backtrack func(start int, current []int)
backtrack = func(start int, current []int) {
if len(current) == k {
tmp := make([]int, k); copy(tmp, current)
result = append(result, tmp); return
}
// Pruning: need (k - len(current)) more, so stop if not enough remain
for i := start; i <= n-(k-len(current))+1; i++ {
current = append(current, i)
backtrack(i+1, current)
current = current[:len(current)-1]
}
}
backtrack(1, []int{})
return result
}
// ── Combination Sum (unlimited reuse, unique candidates) ──
func combinationSum(candidates []int, target int) [][]int {
sort.Ints(candidates)
var result [][]int
var backtrack func(start, remaining int, current []int)
backtrack = func(start, remaining int, current []int) {
if remaining == 0 {
tmp := make([]int, len(current)); copy(tmp, current)
result = append(result, tmp); return
}
for i := start; i < len(candidates); i++ {
if candidates[i] > remaining { break } // pruning: sorted, so rest are bigger
current = append(current, candidates[i])
backtrack(i, remaining-candidates[i], current) // i (not i+1) = allow reuse
current = current[:len(current)-1]
}
}
backtrack(0, target, []int{})
return result
}
// ── Permutations (no duplicates) ─────────────────────────
func permute(nums []int) [][]int {
var result [][]int
var backtrack func(start int)
backtrack = func(start int) {
if start == len(nums) {
tmp := make([]int, len(nums)); copy(tmp, nums)
result = append(result, tmp); return
}
for i := start; i < len(nums); i++ {
nums[start], nums[i] = nums[i], nums[start] // swap in
backtrack(start + 1)
nums[start], nums[i] = nums[i], nums[start] // swap out (undo)
}
}
backtrack(0)
return result
}
// ── Permutations II (with duplicates) ─────────────────────
func permuteUnique(nums []int) [][]int {
sort.Ints(nums)
used := make([]bool, len(nums))
var result [][]int
var backtrack func(current []int)
backtrack = func(current []int) {
if len(current) == len(nums) {
tmp := make([]int, len(current)); copy(tmp, current)
result = append(result, tmp); return
}
for i := 0; i < len(nums); i++ {
if used[i] { continue }
// Skip duplicate: same value, previous sibling not used
if i > 0 && nums[i] == nums[i-1] && !used[i-1] { continue }
used[i] = true
backtrack(append(current, nums[i]))
used[i] = false
}
}
backtrack([]int{})
return result
}
func main() {
fmt.Println(len(subsets([]int{1,2,3}))) // 8
fmt.Println(subsetsWithDup([]int{1,2,2})) // 6 unique
fmt.Println(combine(4, 2)) // C(4,2) = 6
fmt.Println(combinationSum([]int{2,3,6,7}, 7)) // [[2 2 3] [7]]
fmt.Println(len(permute([]int{1,2,3}))) // 6
fmt.Println(len(permuteUnique([]int{1,1,2}))) // 3
}
package main
import (
"fmt"
"strings"
)
// ── N-Queens ──────────────────────────────────────────────
func solveNQueens(n int) [][]string {
var result [][]string
board := make([][]byte, n)
for i := range board { board[i] = []byte(strings.Repeat(".", n)) }
cols := make([]bool, n)
diag1 := make([]bool, 2*n) // row-col+n: top-left diagonals
diag2 := make([]bool, 2*n) // row+col: top-right diagonals
var backtrack func(row int)
backtrack = func(row int) {
if row == n {
snapshot := make([]string, n)
for i, r := range board { snapshot[i] = string(r) }
result = append(result, snapshot)
return
}
for col := 0; col < n; col++ {
if cols[col] || diag1[row-col+n] || diag2[row+col] { continue }
board[row][col] = 'Q'
cols[col], diag1[row-col+n], diag2[row+col] = true, true, true
backtrack(row + 1)
board[row][col] = '.'
cols[col], diag1[row-col+n], diag2[row+col] = false, false, false
}
}
backtrack(0)
return result
}
// ── Sudoku Solver ─────────────────────────────────────────
func solveSudoku(board [][]byte) {
rows := [9][9]bool{}
cols := [9][9]bool{}
boxes := [9][9]bool{}
var empty [][2]int
for r := 0; r < 9; r++ {
for c := 0; c < 9; c++ {
if board[r][c] == '.' { empty = append(empty, [2]int{r, c}); continue }
d := board[r][c] - '1'
rows[r][d], cols[c][d], boxes[(r/3)*3+c/3][d] = true, true, true
}
}
var solve func(idx int) bool
solve = func(idx int) bool {
if idx == len(empty) { return true }
r, c := empty[idx][0], empty[idx][1]
box := (r/3)*3 + c/3
for d := 0; d < 9; d++ {
if rows[r][d] || cols[c][d] || boxes[box][d] { continue }
board[r][c] = byte('1' + d)
rows[r][d], cols[c][d], boxes[box][d] = true, true, true
if solve(idx + 1) { return true }
board[r][c] = '.'
rows[r][d], cols[c][d], boxes[box][d] = false, false, false
}
return false
}
solve(0)
}
// ── Word Search (grid) ────────────────────────────────────
func exist(board [][]byte, word string) bool {
m, n := len(board), len(board[0])
var dfs func(r, c, idx int) bool
dfs = func(r, c, idx int) bool {
if idx == len(word) { return true }
if r < 0 || r >= m || c < 0 || c >= n { return false }
if board[r][c] != word[idx] { return false }
tmp := board[r][c]
board[r][c] = '#' // mark visited in-place
found := dfs(r+1,c,idx+1) || dfs(r-1,c,idx+1) ||
dfs(r,c+1,idx+1) || dfs(r,c-1,idx+1)
board[r][c] = tmp // restore
return found
}
for r := 0; r < m; r++ {
for c := 0; c < n; c++ {
if dfs(r, c, 0) { return true }
}
}
return false
}
func main() {
fmt.Println(len(solveNQueens(4))) // 2 solutions
fmt.Println(solveNQueens(4)[0])
grid := [][]byte{
{'A','B','C','E'},
{'S','F','C','S'},
{'A','D','E','E'},
}
fmt.Println(exist(grid, "ABCCED")) // true
fmt.Println(exist(grid, "SEE")) // true
fmt.Println(exist(grid, "ABCB")) // false
}
make(choice) must have a corresponding undo(choice) after the recursive call. Missing the undo is the #1 backtracking bug. Using boolean arrays (N-Queens cols/diags) makes undo trivial. For grids, temporarily mark with a sentinel character and restore it.
nums[i] == nums[i-1] at the same recursion level (same start). The condition is subtle: i > start && nums[i] == nums[i-1] for subsets; i > 0 && nums[i] == nums[i-1] && !used[i-1] for permutations.
Greedy Algorithms
An algorithm that makes the locally optimal choice at each step, trusting that local optima compose into a global optimum. Greedy is faster than DP but only correct when the problem has the greedy choice property — choosing greedily never eliminates the optimal solution.
Making change with coins: always pick the largest coin that fits. With standard denominations [25,10,5,1] this always gives the minimum coin count — that's why greedy works. With unusual denominations like [1,3,4] and target 6, greedy picks 4+1+1=3 coins, but optimal is 3+3=2 — greedy fails, DP required.
Two common structural patterns: (1) Sort then scan: sort by a key that enforces the greedy property (end time, frequency, weight), then make one linear pass of decisions. (2) Two-pass: enforce one directional constraint left-to-right, then the opposite right-to-left. Proving correctness uses an exchange argument: assume optimal differs, show swapping to greedy doesn't worsen it.
Scheduling/interval problems (sort by end time). Minimum spanning tree (Kruskal's, Prim's). Huffman encoding. Activity selection. Any problem where you can prove "always taking the best-looking option now can't hurt later." When uncertain — try DP first, then optimise.
Greedy makes the locally optimal choice at each step, trusting it leads to a global optimum. Proving correctness requires showing an exchange argument or greedy stays ahead. Hard to see; easy to code once you see it.
| Problem | Greedy Choice | Why it works |
|---|---|---|
| Activity/Interval Scheduling | Pick earliest finish time | Leaves max room for future activities |
| Jump Game | Track max reachable index | If reachable, always optimal to extend |
| Candy (ascending/descending) | Two-pass left-right + right-left | Each pass enforces one direction's constraint |
| Gas Station | Start where tank never goes negative | If total gas ≥ total cost, solution exists |
| Task Scheduler | Most frequent task fills idle slots | Most frequent determines minimum idle time |
| Partition Labels | Extend window to last occurrence of char | Greedy window = tightest valid partition |
package main
import (
"fmt"
"sort"
)
// ── Interval Scheduling — max non-overlapping intervals ───
func eraseOverlapIntervals(intervals [][2]int) int {
sort.Slice(intervals, func(i, j int) bool {
return intervals[i][1] < intervals[j][1] // sort by END time
})
removed, end := 0, -1<<62
for _, iv := range intervals {
if iv[0] >= end {
end = iv[1] // take this interval
} else {
removed++ // overlaps — remove it
}
}
return removed
}
// ── Gas Station ───────────────────────────────────────────
// If total gas >= total cost, solution exists. Find the start
// where running tank never dips below 0.
func canCompleteCircuit(gas, cost []int) int {
total, tank, start := 0, 0, 0
for i := range gas {
diff := gas[i] - cost[i]
total += diff
tank += diff
if tank < 0 { start = i + 1; tank = 0 } // reset
}
if total < 0 { return -1 }
return start
}
// ── Candy — each child gets min 1 candy; higher rating ────
// than neighbour gets more candy. Min candies total.
func candy(ratings []int) int {
n := len(ratings)
candies := make([]int, n)
for i := range candies { candies[i] = 1 }
// Left pass: right neighbour with higher rating gets +1
for i := 1; i < n; i++ {
if ratings[i] > ratings[i-1] { candies[i] = candies[i-1] + 1 }
}
// Right pass: left neighbour with higher rating gets max of current or +1
for i := n - 2; i >= 0; i-- {
if ratings[i] > ratings[i+1] && candies[i] <= candies[i+1] {
candies[i] = candies[i+1] + 1
}
}
total := 0
for _, c := range candies { total += c }
return total
}
// ── Task Scheduler ────────────────────────────────────────
// Minimum intervals to execute all tasks with cooldown n
func leastInterval(tasks []byte, n int) int {
freq := [26]int{}
for _, t := range tasks { freq[t-'A']++ }
sort.Ints(freq[:])
maxFreq := freq[25]
// Slots = (maxFreq-1) * (n+1) + count of tasks with maxFreq
idleSlots := (maxFreq - 1) * (n + 1)
for _, f := range freq { if f == maxFreq { idleSlots++ } }
if idleSlots > len(tasks) { return idleSlots }
return len(tasks) // tasks fill all idle slots
}
// ── Partition Labels ──────────────────────────────────────
// Partition string so each letter appears in at most 1 part
func partitionLabels(s string) []int {
last := [26]int{}
for i, c := range s { last[c-'a'] = i } // last occurrence of each char
var result []int
start, end := 0, 0
for i, c := range s {
if pos := last[c-'a']; pos > end { end = pos } // extend window
if i == end { // current window is complete
result = append(result, end-start+1)
start = end + 1
}
}
return result
}
// ── Boats to Save People ──────────────────────────────────
// Each boat holds <= limit weight, at most 2 people
func numRescueBoats(people []int, limit int) int {
sort.Ints(people)
l, r, boats := 0, len(people)-1, 0
for l <= r {
if people[l]+people[r] <= limit { l++ } // lightest can share with heaviest
r--; boats++ // heaviest always gets a boat
}
return boats
}
// ── Assign Cookies ────────────────────────────────────────
// Greedily satisfy smallest greed with smallest sufficient cookie
func findContentChildren(greed, size []int) int {
sort.Ints(greed); sort.Ints(size)
child, cookie := 0, 0
for child < len(greed) && cookie < len(size) {
if size[cookie] >= greed[child] { child++ } // satisfied
cookie++
}
return child
}
// ── Wiggle Subsequence ────────────────────────────────────
// Length of longest wiggle subsequence (greedy count peaks/valleys)
func wiggleMaxLength(nums []int) int {
if len(nums) < 2 { return len(nums) }
length, prevDiff := 1, 0
for i := 1; i < len(nums); i++ {
diff := nums[i] - nums[i-1]
if (diff > 0 && prevDiff <= 0) || (diff < 0 && prevDiff >= 0) {
length++
prevDiff = diff
}
}
return length
}
func main() {
ivs := [][2]int{{1,2},{2,3},{3,4},{1,3}}
fmt.Println(eraseOverlapIntervals(ivs)) // 1
fmt.Println(canCompleteCircuit([]int{1,2,3,4,5}, []int{3,4,5,1,2})) // 3
fmt.Println(candy([]int{1,0,2})) // 5
fmt.Println(candy([]int{1,2,2})) // 4
fmt.Println(leastInterval([]byte("AABABC"), 2)) // 8 (wait: ABCAB_AB or similar)
// Note: 'A'=3,'B'=2,'C'=1 → (3-1)*(2+1)+1=7, len=6 → max(7,6)=7
fmt.Println(partitionLabels("ababcbacadefegdehijhklij")) // [9 7 8]
fmt.Println(numRescueBoats([]int{3,2,2,1}, 3)) // 3
fmt.Println(findContentChildren([]int{1,2,3}, []int{1,1})) // 1
fmt.Println(wiggleMaxLength([]int{1,7,4,9,2,5})) // 6
}
Trie (Prefix Tree)
A tree where each node represents a character in a string, and paths from root to marked nodes spell out stored words. All words sharing a prefix share the same path — making prefix operations O(L) regardless of how many words are stored.
Autocomplete in a search bar. As you type each letter, the system narrows down suggestions. Every keystroke is one level deeper in the trie — "app" leads to "apple", "application", "appetite" all sharing the "app" prefix node. No wasted work re-scanning previous characters.
Each node has an array (or map) of 26 child pointers plus an isEnd flag. Insert: walk one character at a time, creating nodes as needed. Search: walk and return false on missing child. Use [26]*TrieNode for O(1) lowercase-alpha access. Use map[rune]*Node for sparse/Unicode alphabets.
Autocomplete / prefix search. Word dictionary with wildcard (.). Word Search II (trie + DFS — far faster than brute-force one-word-at-a-time search). IP routing (longest prefix match). Counting words with a given prefix. Whenever multiple string operations share prefixes.
c→a path. The trie does not re-scan every word for each new character.children [26]*TrieNode (or map) and isEnd bool. Insert: walk character by character, create missing nodes. Search: walk and check isEnd at the last char. StartsWith: walk and return true if path exists (don't check isEnd).A Trie stores strings character by character, sharing prefixes. Insert/Search/StartsWith are all O(L) where L = word length. Space is O(ALPHABET × total_chars). Used for autocomplete, IP routing, spell check.
package main
import "fmt"
// ── Standard Trie (26 lowercase letters) ─────────────────
type TrieNode struct {
children [26]*TrieNode
isEnd bool
// Optional extras often needed in interviews:
count int // how many words pass through this node
word string // store word at leaf (useful for Word Search II)
}
type Trie struct{ root *TrieNode }
func NewTrie() *Trie { return &Trie{root: &TrieNode{}} }
func (t *Trie) Insert(word string) {
node := t.root
for _, c := range word {
idx := c - 'a'
if node.children[idx] == nil {
node.children[idx] = &TrieNode{}
}
node = node.children[idx]
node.count++ // count words with this prefix
}
node.isEnd = true
node.word = word
}
func (t *Trie) Search(word string) bool {
node := t.find(word)
return node != nil && node.isEnd
}
func (t *Trie) StartsWith(prefix string) bool {
return t.find(prefix) != nil
}
func (t *Trie) CountWithPrefix(prefix string) int {
node := t.find(prefix)
if node == nil { return 0 }
return node.count
}
func (t *Trie) find(prefix string) *TrieNode {
node := t.root
for _, c := range prefix {
idx := c - 'a'
if node.children[idx] == nil { return nil }
node = node.children[idx]
}
return node
}
// Delete word from trie
func (t *Trie) Delete(word string) bool {
return t.deleteHelper(t.root, word, 0)
}
func (t *Trie) deleteHelper(node *TrieNode, word string, depth int) bool {
if node == nil { return false }
if depth == len(word) {
if !node.isEnd { return false }
node.isEnd = false
node.word = ""
return true // caller may prune this node if no children
}
idx := word[depth] - 'a'
if t.deleteHelper(node.children[idx], word, depth+1) {
node.count--
// Prune leaf if no longer needed
if !node.children[idx].isEnd && node.children[idx].count == 0 {
node.children[idx] = nil
}
return true
}
return false
}
// ── Trie with wildcard '.' (Word Dictionary) ─────────────
type WordDictionary struct{ root *TrieNode }
func NewWordDictionary() *WordDictionary { return &WordDictionary{&TrieNode{}} }
func (d *WordDictionary) AddWord(word string) {
node := d.root
for _, c := range word {
idx := c - 'a'
if node.children[idx] == nil { node.children[idx] = &TrieNode{} }
node = node.children[idx]
}
node.isEnd = true
}
func (d *WordDictionary) Search(word string) bool {
return d.searchFrom(d.root, word, 0)
}
func (d *WordDictionary) searchFrom(node *TrieNode, word string, idx int) bool {
if node == nil { return false }
if idx == len(word) { return node.isEnd }
c := word[idx]
if c != '.' {
return d.searchFrom(node.children[c-'a'], word, idx+1)
}
// Wildcard: try all 26 children
for _, child := range node.children {
if d.searchFrom(child, word, idx+1) { return true }
}
return false
}
// ── Map-based Trie (simpler, supports any alphabet) ───────
type MapTrieNode struct {
children map[rune]*MapTrieNode
isEnd bool
}
func NewMapTrie() *MapTrieNode { return &MapTrieNode{children: make(map[rune]*MapTrieNode)} }
func (n *MapTrieNode) Insert(word string) {
curr := n
for _, c := range word {
if _, ok := curr.children[c]; !ok {
curr.children[c] = &MapTrieNode{children: make(map[rune]*MapTrieNode)}
}
curr = curr.children[c]
}
curr.isEnd = true
}
func (n *MapTrieNode) Search(word string) bool {
curr := n
for _, c := range word {
if _, ok := curr.children[c]; !ok { return false }
curr = curr.children[c]
}
return curr.isEnd
}
func main() {
t := NewTrie()
t.Insert("apple"); t.Insert("app"); t.Insert("application")
fmt.Println(t.Search("apple")) // true
fmt.Println(t.Search("app")) // true
fmt.Println(t.Search("appl")) // false
fmt.Println(t.StartsWith("appl")) // true
fmt.Println(t.CountWithPrefix("app")) // 3
t.Delete("app")
fmt.Println(t.Search("app")) // false
fmt.Println(t.StartsWith("app")) // true (apple still exists)
d := NewWordDictionary()
d.AddWord("bad"); d.AddWord("dad"); d.AddWord("mad")
fmt.Println(d.Search("pad")) // false
fmt.Println(d.Search("bad")) // true
fmt.Println(d.Search(".ad")) // true
fmt.Println(d.Search("b..")) // true
}
package main
import "fmt"
type WNode struct {
children [26]*WNode
word string // non-empty at terminal node
}
// Find all words from list that exist in the grid
func findWords(board [][]byte, words []string) []string {
root := &WNode{}
// Build trie from word list
for _, w := range words {
node := root
for _, c := range w {
idx := c - 'a'
if node.children[idx] == nil { node.children[idx] = &WNode{} }
node = node.children[idx]
}
node.word = w
}
m, n := len(board), len(board[0])
var result []string
var dfs func(node *WNode, r, c int)
dfs = func(node *WNode, r, c int) {
if r < 0 || r >= m || c < 0 || c >= n { return }
ch := board[r][c]
if ch == '#' { return } // already visited
next := node.children[ch-'a']
if next == nil { return } // no words with this prefix
if next.word != "" {
result = append(result, next.word)
next.word = "" // deduplicate: don't add same word twice
}
board[r][c] = '#'
dfs(next, r+1, c); dfs(next, r-1, c)
dfs(next, r, c+1); dfs(next, r, c-1)
board[r][c] = ch
}
for r := 0; r < m; r++ {
for c := 0; c < n; c++ {
dfs(root, r, c)
}
}
return result
}
func main() {
board := [][]byte{
{'o','a','a','n'},
{'e','t','a','e'},
{'i','h','k','r'},
{'i','f','l','v'},
}
words := []string{"oath","pea","eat","rain"}
fmt.Println(findWords(board, words)) // [oath eat]
}
[26]*TrieNode is fast (O(1) access, cache-friendly) but wastes memory for sparse alphabets. map[rune]*TrieNode handles Unicode and sparse data but has higher constant. Pick [26] for lowercase-alpha problems, map for general strings.
node.word = "" to deduplicate without removing from trie. (3) Prune trie branches as you find all words under them — reduces DFS overhead on large boards.
Matrices
A 2D grid of values, treated as an implicit graph where each cell (r,c) is a node and edges connect it to its 4 (or 8) neighbours. Most matrix problems reduce to graph traversal — choose DFS for component/path problems, BFS for shortest-distance problems.
A city grid: each intersection is a cell, roads go north/south/east/west (4-directional). Finding connected islands is finding city blocks. Rotting oranges is a virus spreading through the grid. Pacific Atlantic is water flowing downhill — reverse-BFS from the oceans uphill.
Cell (r,c) maps to node index r*cols + c for Union-Find. Direction arrays (dirs4, dirs8) loop over neighbours cleanly. Mark visited by overwriting the cell (common DFS trick). Multi-source BFS seeds the queue with all sources at distance 0 — the wave front expands simultaneously from all of them.
Island counting / flood fill → DFS overwrite. Shortest path / min steps → BFS. Multi-source spread (rotting, fire) → multi-source BFS. Border-connected cells (surrounded regions, Pacific-Atlantic) → reverse BFS/DFS from borders. Dynamic connectivity → Union-Find with r*cols+c node encoding.
[r][c] can be treated as a graph node. Edges connect each cell to its 4 (or 8) neighbours. Nearly all matrix problems reduce to a graph algorithm — the skill is choosing the right one (DFS, BFS, Union-Find) and encoding state efficiently.(r,c) → node index r*cols + c for Union-Find. Direction array dirs4 = [][2]int{{0,1},{0,-1},{1,0},{-1,0}} iterates neighbours. Always bounds-check before accessing. Mark visited by overwriting cell value (avoids extra boolean grid) — restore if needed (backtracking).Grid problems are graphs in disguise. Each cell (r,c) is a node; edges go to 4 (or 8) neighbours. The key is choosing DFS vs BFS and setting up the direction array correctly.
package main
import "fmt"
// ── Direction arrays ──────────────────────────────────────
var dirs4 = [][2]int{{0,1},{0,-1},{1,0},{-1,0}} // 4-directional
var dirs8 = [][2]int{{0,1},{0,-1},{1,0},{-1,0},{1,1},{1,-1},{-1,1},{-1,-1}} // 8-directional
func inBounds(r, c, m, n int) bool { return r >= 0 && r < m && c >= 0 && c < n }
// ── DFS flood fill ────────────────────────────────────────
func floodFill(image [][]int, sr, sc, color int) [][]int {
src := image[sr][sc]
if src == color { return image }
var dfs func(r, c int)
dfs = func(r, c int) {
if !inBounds(r, c, len(image), len(image[0])) { return }
if image[r][c] != src { return }
image[r][c] = color
for _, d := range dirs4 { dfs(r+d[0], c+d[1]) }
}
dfs(sr, sc)
return image
}
// ── BFS: 01 Matrix — distance to nearest 0 ───────────────
func updateMatrix(mat [][]int) [][]int {
m, n := len(mat), len(mat[0])
dist := make([][]int, m)
queue := [][2]int{}
for i := range dist {
dist[i] = make([]int, n)
for j := range dist[i] {
if mat[i][j] == 0 {
dist[i][j] = 0
queue = append(queue, [2]int{i, j})
} else {
dist[i][j] = 1<<30
}
}
}
for len(queue) > 0 {
cur := queue[0]; queue = queue[1:]
for _, d := range dirs4 {
r, c := cur[0]+d[0], cur[1]+d[1]
if inBounds(r, c, m, n) && dist[r][c] > dist[cur[0]][cur[1]]+1 {
dist[r][c] = dist[cur[0]][cur[1]] + 1
queue = append(queue, [2]int{r, c})
}
}
}
return dist
}
// ── BFS: Rotting Oranges — minimum time ──────────────────
func orangesRotting(grid [][]int) int {
m, n := len(grid), len(grid[0])
fresh, minutes := 0, 0
queue := [][2]int{}
for i := 0; i < m; i++ {
for j := 0; j < n; j++ {
switch grid[i][j] {
case 1: fresh++
case 2: queue = append(queue, [2]int{i, j})
}
}
}
for len(queue) > 0 && fresh > 0 {
size := len(queue)
for k := 0; k < size; k++ {
r, c := queue[k][0], queue[k][1]
for _, d := range dirs4 {
nr, nc := r+d[0], c+d[1]
if inBounds(nr, nc, m, n) && grid[nr][nc] == 1 {
grid[nr][nc] = 2
fresh--
queue = append(queue, [2]int{nr, nc})
}
}
}
queue = queue[size:]
minutes++
}
if fresh > 0 { return -1 }
return minutes
}
// ── Pacific Atlantic Water Flow ───────────────────────────
// Water can flow to cell with equal or lower height
// Find cells that can reach BOTH oceans
func pacificAtlantic(heights [][]int) [][2]int {
m, n := len(heights), len(heights[0])
pacific := make([][]bool, m)
atlantic := make([][]bool, m)
for i := range pacific { pacific[i] = make([]bool, n); atlantic[i] = make([]bool, n) }
var bfs func(queue [][2]int, visited [][]bool)
bfs = func(queue [][2]int, visited [][]bool) {
for len(queue) > 0 {
cur := queue[0]; queue = queue[1:]
for _, d := range dirs4 {
r, c := cur[0]+d[0], cur[1]+d[1]
if inBounds(r, c, m, n) && !visited[r][c] &&
heights[r][c] >= heights[cur[0]][cur[1]] { // reverse flow: go uphill
visited[r][c] = true
queue = append(queue, [2]int{r, c})
}
}
}
}
// Seed Pacific (top row + left col) and Atlantic (bottom row + right col)
pQueue, aQueue := [][2]int{}, [][2]int{}
for i := 0; i < m; i++ {
pacific[i][0] = true; pQueue = append(pQueue, [2]int{i, 0})
atlantic[i][n-1] = true; aQueue = append(aQueue, [2]int{i, n-1})
}
for j := 0; j < n; j++ {
pacific[0][j] = true; pQueue = append(pQueue, [2]int{0, j})
atlantic[m-1][j] = true; aQueue = append(aQueue, [2]int{m-1, j})
}
bfs(pQueue, pacific); bfs(aQueue, atlantic)
var result [][2]int
for i := 0; i < m; i++ {
for j := 0; j < n; j++ {
if pacific[i][j] && atlantic[i][j] { result = append(result, [2]int{i, j}) }
}
}
return result
}
// ── Surrounded Regions — capture 'O' not connected to edge ─
func solve(board [][]byte) {
m, n := len(board), len(board[0])
var dfs func(r, c int)
dfs = func(r, c int) {
if !inBounds(r, c, m, n) || board[r][c] != 'O' { return }
board[r][c] = 'S' // safe — connected to border
for _, d := range dirs4 { dfs(r+d[0], c+d[1]) }
}
// Mark all 'O's connected to border as safe
for i := 0; i < m; i++ { dfs(i, 0); dfs(i, n-1) }
for j := 0; j < n; j++ { dfs(0, j); dfs(m-1, j) }
// Flip: remaining 'O' → 'X', safe 'S' → 'O'
for i := 0; i < m; i++ {
for j := 0; j < n; j++ {
switch board[i][j] {
case 'O': board[i][j] = 'X'
case 'S': board[i][j] = 'O'
}
}
}
}
// ── Number of Distinct Islands (shape hashing) ───────────
func numDistinctIslands(grid [][]byte) int {
m, n := len(grid), len(grid[0])
shapes := make(map[string]bool)
var shape []byte
var dfs func(r, c, br, bc int)
dfs = func(r, c, br, bc int) {
if !inBounds(r, c, m, n) || grid[r][c] != '1' { return }
grid[r][c] = '0'
shape = append(shape, byte(r-br+'0'), byte(c-bc+'0'), ',') // relative position
for _, d := range dirs4 { dfs(r+d[0], c+d[1], br, bc) }
shape = append(shape, '!') // backtrack marker (distinguishes shapes)
}
for i := 0; i < m; i++ {
for j := 0; j < n; j++ {
if grid[i][j] == '1' {
shape = shape[:0]
dfs(i, j, i, j)
shapes[string(shape)] = true
}
}
}
return len(shapes)
}
func main() {
img := [][]int{{1,1,1},{1,1,0},{1,0,1}}
fmt.Println(floodFill(img, 1, 1, 2)) // [[2 2 2] [2 2 0] [2 0 1]]
mat := [][]int{{0,0,0},{0,1,0},{1,1,1}}
fmt.Println(updateMatrix(mat)) // [[0 0 0] [0 1 0] [1 2 1]]
grid := [][]int{{2,1,1},{1,1,0},{0,1,1}}
fmt.Println(orangesRotting(grid)) // 4
heights := [][]int{{1,2,2,3,5},{3,2,3,4,4},{2,4,5,3,1},{6,7,1,4,5},{5,1,1,2,4}}
fmt.Println(pacificAtlantic(heights)) // cells reaching both oceans
}
package main
import "fmt"
// ── Union-Find for grid connectivity ──────────────────────
type UnionFind struct {
parent, rank []int
components int
}
func newUF(n int) *UnionFind {
uf := &UnionFind{parent: make([]int, n), rank: make([]int, n), components: n}
for i := range uf.parent { uf.parent[i] = i }
return uf
}
func (uf *UnionFind) find(x int) int {
if uf.parent[x] != x { uf.parent[x] = uf.find(uf.parent[x]) } // path compression
return uf.parent[x]
}
func (uf *UnionFind) union(x, y int) {
rx, ry := uf.find(x), uf.find(y)
if rx == ry { return }
uf.components--
if uf.rank[rx] < uf.rank[ry] { rx, ry = ry, rx }
uf.parent[ry] = rx
if uf.rank[rx] == uf.rank[ry] { uf.rank[rx]++ }
}
// Number of Islands using Union-Find
func numIslandsUF(grid [][]byte) int {
m, n := len(grid), len(grid[0])
uf := newUF(m * n)
islandCount := 0
for i := 0; i < m; i++ {
for j := 0; j < n; j++ {
if grid[i][j] == '1' {
islandCount++
if i > 0 && grid[i-1][j] == '1' { uf.union(i*n+j, (i-1)*n+j); islandCount-- }
if j > 0 && grid[i][j-1] == '1' { uf.union(i*n+j, i*n+j-1); islandCount-- }
}
}
}
return islandCount
}
// ── Diagonal traversal ────────────────────────────────────
// Traverse all diagonals of m×n matrix (anti-diagonal: r+c=const)
func findDiagonalOrder(mat [][]int) []int {
m, n := len(mat), len(mat[0])
result := make([]int, 0, m*n)
diags := make(map[int][]int)
for i := 0; i < m; i++ {
for j := 0; j < n; j++ {
diags[i+j] = append(diags[i+j], mat[i][j])
}
}
for d := 0; d < m+n-1; d++ {
if d%2 == 0 { // go up-right: reverse the diagonal
for k := len(diags[d])-1; k >= 0; k-- { result = append(result, diags[d][k]) }
} else {
result = append(result, diags[d]...)
}
}
return result
}
// ── Valid path in grid with obstacles ─────────────────────
// 0 = free, 1 = obstacle
func hasPathInGrid(grid [][]int) bool {
m, n := len(grid), len(grid[0])
if grid[0][0] == 1 || grid[m-1][n-1] == 1 { return false }
queue := [][2]int{{0, 0}}
grid[0][0] = 1 // mark visited
for len(queue) > 0 {
r, c := queue[0][0], queue[0][1]; queue = queue[1:]
if r == m-1 && c == n-1 { return true }
for _, d := range dirs4 {
nr, nc := r+d[0], c+d[1]
if inBounds(nr, nc, m, n) && grid[nr][nc] == 0 {
grid[nr][nc] = 1
queue = append(queue, [2]int{nr, nc})
}
}
}
return false
}
var dirs4 = [][2]int{{0,1},{0,-1},{1,0},{-1,0}}
func inBounds(r, c, m, n int) bool { return r >= 0 && r < m && c >= 0 && c < n }
func main() {
g := [][]byte{{'1','1','0'},{'0','1','1'},{'0','0','1'}}
fmt.Println(numIslandsUF(g)) // 1
mat := [][]int{{1,2,3},{4,5,6},{7,8,9}}
fmt.Println(findDiagonalOrder(mat)) // [1 2 4 7 5 3 6 8 9]
}
grid[nr][nc] = 2; queue = append(queue, ...) together.
heights[r][c] >= heights[parent]). Cells reachable from both seedings are the answer.
(r,c) to node index as r*cols + c.
Go Traps & Gotchas
Go-specific runtime panics, silent data bugs, and non-obvious language behaviours that trip up even experienced developers. Each trap has a mechanical root cause in Go's type system, memory model, or scheduler — understanding the cause makes the fix obvious.
Hidden potholes on a road you drive daily. Most journeys are fine. But certain combinations — nil map write, interface with nil pointer, send on closed channel — are specific coordinates that always crash. Knowing their exact locations means you never hit them.
Each trap has a specific cause: nil map panics because Go maps are reference types that must be initialised. Interface-nil mismatch because an interface stores (type, value) — a typed nil is not the zero interface. Closure capture shares the variable's memory address, not its current value.
Review this section before every Go interview. In code reviews, scan specifically for: unguarded map writes, slice append results that are discarded, goroutines in loops with captured variables, defer in loops, and functions returning concrete error types.
Every item here has appeared in real Go interviews or caused production bugs. Know them cold.
package main
import (
"fmt"
"reflect"
"sync"
)
// ── TRAP 1: Nil map write ──────────────────────────────────
func trapNilMap() {
var m map[string]int
fmt.Println(m["key"]) // OK — returns 0
// m["key"] = 1 // PANIC: assignment to entry in nil map
m = make(map[string]int)
m["key"] = 1 // now OK
}
// ── TRAP 2: Slice aliasing after append ────────────────────
func trapSliceAlias() {
a := make([]int, 3, 6) // len=3, cap=6
b := a[:] // shares underlying array
b = append(b, 99) // cap not exceeded → modifies a's backing array
fmt.Println(a[:4]) // [0 0 0 99] — surprise!
// Safe: use copy, or 3-index slice to cap sharing
c := a[:3:3] // cap == len → next append always allocates
c = append(c, 99) // new array allocated
fmt.Println(a[:4]) // [0 0 0 99] — unchanged
_ = b; _ = c
}
// ── TRAP 3: For range copies value ────────────────────────
func trapRangeCopy() {
type Person struct{ Name string; Age int }
people := []Person{{"Alice", 30}, {"Bob", 25}}
// WRONG — v is a copy
for _, v := range people {
v.Age++ // modifies copy, not original
}
fmt.Println(people[0].Age) // 30 — unchanged!
// CORRECT — use index
for i := range people {
people[i].Age++
}
fmt.Println(people[0].Age) // 31
}
// ── TRAP 4: Goroutine closure captures loop variable ───────
func trapClosureCapture() {
var wg sync.WaitGroup
results := make([]int, 5)
// WRONG (pre Go 1.22)
// for i := 0; i < 5; i++ {
// wg.Add(1)
// go func() { defer wg.Done(); results[i] = i }()
// }
// CORRECT: pass as argument
for i := 0; i < 5; i++ {
wg.Add(1)
go func(i int) { defer wg.Done(); results[i] = i * i }(i)
}
wg.Wait()
fmt.Println(results) // [0 1 4 9 16]
}
// ── TRAP 5: Interface nil vs typed nil ────────────────────
type MyError struct{ msg string }
func (e *MyError) Error() string { return e.msg }
func mightFail(fail bool) error {
var e *MyError = nil
if fail { e = &MyError{"oops"} }
return e // returns (*MyError, nil) — non-nil interface!
}
func trapInterfaceNil() {
err := mightFail(false)
fmt.Println(err) //
fmt.Println(err == nil) // FALSE — interface holds (*MyError, nil)!
fmt.Println(reflect.ValueOf(err).IsNil()) // true
// FIX: return untyped nil
// if fail { return &MyError{} }; return nil
}
// ── TRAP 6: Defer evaluates args immediately ───────────────
func trapDeferArgs() {
x := 0
defer fmt.Println("defer x:", x) // captures x=0 now
x = 42
fmt.Println("body x:", x) // 42
// Output: body x: 42, then defer x: 0
}
// ── TRAP 7: Named return + defer ──────────────────────────
func trapNamedReturn() (result int) {
defer func() {
result++ // modifies the named return variable!
}()
return 10 // sets result=10, then defer runs → result=11
}
// ── TRAP 8: String indexing gives bytes, not runes ─────────
func trapStringIndex() {
s := "日本語"
fmt.Println(len(s)) // 9 bytes
fmt.Println(s[0]) // 230 (first byte of 日)
for i, r := range s {
fmt.Printf("index=%d rune=%c\n", i, r)
}
// index=0 rune=日, index=3 rune=本, index=6 rune=語
}
// ── TRAP 9: Integer overflow ────────────────────────────────
func trapOverflow() {
var i int32 = 2147483647 // MaxInt32
i++ // overflows to -2147483648 silently!
fmt.Println(i) // -2147483648
// In interviews: always ask about constraints, use int64
var safe int64 = 2147483647
safe++
fmt.Println(safe) // 2147483648 — fine
}
// ── TRAP 10: Defer in loop ─────────────────────────────────
func trapDeferLoop() {
// WRONG: defers pile up, all run at function exit
for i := 0; i < 3; i++ {
defer fmt.Println("deferred:", i) // prints 2, 1, 0 at END
}
// FIX: wrap in closure/function
for i := 0; i < 3; i++ {
func(i int) {
defer fmt.Println("wrapped defer:", i) // runs immediately
}(i)
}
}
func main() {
fmt.Println("=== Nil Map ==="); trapNilMap()
fmt.Println("=== Slice Alias ==="); trapSliceAlias()
fmt.Println("=== Range Copy ==="); trapRangeCopy()
fmt.Println("=== Closure ==="); trapClosureCapture()
fmt.Println("=== Interface Nil ==="); trapInterfaceNil()
fmt.Println("=== Defer Args ==="); trapDeferArgs()
fmt.Println("=== Named Return ==="); fmt.Println(trapNamedReturn()) // 11
fmt.Println("=== String Index ==="); trapStringIndex()
fmt.Println("=== Overflow ==="); trapOverflow()
fmt.Println("=== Defer Loop ==="); trapDeferLoop()
}
CLI Input / Reading Stdin
Mechanisms for a Go program to read structured input from stdin (fmt.Scan, bufio.Scanner), parse command-line arguments (os.Args, flag package), and write output efficiently. Critical for competitive programming and CLI tooling.
The keyboard and screen of your program. Stdin is the keyboard — your program blocks waiting for the user to type. Stdout is the screen — output appears there. Buffering is like typing a whole sentence before hitting send instead of transmitting one character at a time — far faster.
fmt.Scan makes a syscall per token — slow for large input. bufio.NewReader wraps stdin with a 4KB buffer — reads a large chunk once, then parses from memory. bufio.NewWriter accumulates output in memory and flushes in one write. Must call Flush() explicitly — data is lost otherwise.
Competitive programming → always use the bufio.NewReader + bufio.NewWriter + defer Flush() template. Interactive problems → fmt.Scan is fine. Unknown number of lines → bufio.Scanner with Scan() loop. CLI tools with named flags → flag package with flag.Parse().
Go has several ways to read input. Knowing which to reach for — and when — matters in interviews, competitive coding, and real CLI tools.
| Method | Best For | Gotcha |
|---|---|---|
fmt.Scan / Scanln / Scanf | Simple, small input; interactive programs | Slow on large input; Scanln stops at newline |
bufio.Scanner | Line-by-line reading; unknown number of lines | Default buffer 64KB — must resize for long lines |
bufio.NewReader + fmt.Fscan | Fast whitespace-delimited token reading | Must Flush buffered writer |
os.Args | Command-line arguments | os.Args[0] is the binary name, args start at [1] |
flag package | Named CLI flags (--port 8080) | Must call flag.Parse() before reading values |
Simplest option. Reads from stdin, splits on whitespace. Good for small input or interactive sessions.
package main
import "fmt"
func main() {
// ── fmt.Scan — reads space/newline-separated tokens ──
var a, b int
fmt.Scan(&a, &b) // reads two ints; works across newlines
fmt.Println(a + b)
// ── fmt.Scanln — stops at newline ───────────────────
var x, y float64
fmt.Scanln(&x, &y) // both must be on the SAME line
fmt.Printf("%.2f\n", x+y)
// ── fmt.Scanf — formatted, like C scanf ─────────────
var name string
var age int
fmt.Scanf("%s %d", &name, &age)
fmt.Printf("name=%s age=%d\n", name, age)
// ── Read N numbers on one line ───────────────────────
var n int
fmt.Scan(&n)
nums := make([]int, n)
for i := range nums {
fmt.Scan(&nums[i]) // works even if spread across lines
}
fmt.Println(nums)
// ── Read until EOF (unknown count) ───────────────────
var v int
var values []int
for {
_, err := fmt.Scan(&v)
if err != nil { break } // io.EOF or parse error
values = append(values, v)
}
fmt.Println(values)
}
fmt.Scan treats newlines as whitespace and reads across lines. fmt.Scanln stops at the newline — if you only read 1 of 3 values on a line, the rest are discarded. Use fmt.Scan for most interview problems.
Best when you need to read a whole line as a string, process unknown number of lines, or split on custom delimiters.
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// ── Increase buffer for long lines (default = 64KB) ─
// Required if a single line can be very long
const maxBuf = 1024 * 1024 // 1MB
scanner.Buffer(make([]byte, maxBuf), maxBuf)
// ── Read line by line ────────────────────────────────
var lines []string
for scanner.Scan() {
line := scanner.Text() // current line, no trailing \n
if line == "" { break } // stop on blank line (optional)
lines = append(lines, line)
}
if err := scanner.Err(); err != nil {
fmt.Fprintln(os.Stderr, "read error:", err)
}
fmt.Println(lines)
// ── Read first line as single integer ────────────────
scanner2 := bufio.NewScanner(os.Stdin)
scanner2.Scan()
n, _ := strconv.Atoi(scanner2.Text())
fmt.Println(n)
// ── Read line of space-separated ints ────────────────
scanner3 := bufio.NewScanner(os.Stdin)
scanner3.Scan()
parts := strings.Fields(scanner3.Text()) // splits on any whitespace
nums := make([]int, len(parts))
for i, p := range parts {
nums[i], _ = strconv.Atoi(p)
}
fmt.Println(nums)
// ── Read a grid (matrix) ─────────────────────────────
scanner4 := bufio.NewScanner(os.Stdin)
var grid [][]int
for scanner4.Scan() {
row := []int{}
for _, p := range strings.Fields(scanner4.Text()) {
v, _ := strconv.Atoi(p)
row = append(row, v)
}
if len(row) > 0 { grid = append(grid, row) }
}
fmt.Println(grid)
// ── Split by words instead of lines ─────────────────
scanner5 := bufio.NewScanner(os.Stdin)
scanner5.Split(bufio.ScanWords) // default is ScanLines
for scanner5.Scan() {
fmt.Println(scanner5.Text()) // each word
}
}
The go-to pattern for competitive programming. fmt.Fscan with a buffered reader is ~5-10x faster than plain fmt.Scan on large inputs because it avoids per-call syscalls.
package main
import (
"bufio"
"fmt"
"os"
)
// ── Standard fast I/O template ────────────────────────────
// Copy this boilerplate at the top of every competitive solution
var (
reader = bufio.NewReader(os.Stdin)
writer = bufio.NewWriter(os.Stdout)
)
func main() {
defer writer.Flush() // CRITICAL: flush buffered output at end
// Read a single integer
var n int
fmt.Fscan(reader, &n)
// Read n integers
nums := make([]int, n)
for i := range nums {
fmt.Fscan(reader, &nums[i])
}
// Read a string
var s string
fmt.Fscan(reader, &s) // stops at whitespace
// Read multiple values in one call
var a, b, c int
fmt.Fscan(reader, &a, &b, &c)
// Read a grid of n rows, m cols
var rows, cols int
fmt.Fscan(reader, &rows, &cols)
grid := make([][]int, rows)
for i := range grid {
grid[i] = make([]int, cols)
for j := range grid[i] {
fmt.Fscan(reader, &grid[i][j])
}
}
// Write output (always to writer, not fmt.Println)
fmt.Fprintln(writer, n)
for _, v := range nums {
fmt.Fprintf(writer, "%d ", v)
}
fmt.Fprintln(writer)
fmt.Fprintln(writer, grid)
}
Flush(), buffered output stays in memory and is never written to stdout. Your program produces no output. Always defer writer.Flush() as the very first line of main().
Common input shapes you'll encounter. Each is a self-contained runnable program — pipe input via echo "..." | go run file.go or type interactively.
package main
import (
"bufio"
"fmt"
"os"
"sort"
"strings"
)
var in = bufio.NewReader(os.Stdin)
var out = bufio.NewWriter(os.Stdout)
func main() {
defer out.Flush()
// ── Choose the pattern matching your problem input ───
pattern1() // "read N, then N numbers"
// pattern2() // "read until EOF"
// pattern3() // "read T test cases"
// pattern4() // "read a grid"
// pattern5() // "read edges for a graph"
// pattern6() // "read key-value pairs"
}
// Pattern 1: N followed by N space-separated integers
// Input:
// 5
// 3 1 4 1 5
func pattern1() {
var n int
fmt.Fscan(in, &n)
nums := make([]int, n)
for i := range nums { fmt.Fscan(in, &nums[i]) }
sort.Ints(nums)
fmt.Fprintln(out, nums)
}
// Pattern 2: Read until EOF — unknown number of integers
// Input:
// 1 2 3
// 4 5 6
func pattern2() {
var nums []int
var v int
for {
_, err := fmt.Fscan(in, &v)
if err != nil { break }
nums = append(nums, v)
}
fmt.Fprintln(out, nums)
}
// Pattern 3: T test cases
// Input:
// 3
// 5
// 10
// 15
func pattern3() {
var t int
fmt.Fscan(in, &t)
for ; t > 0; t-- {
var n int
fmt.Fscan(in, &n)
fmt.Fprintln(out, n*2) // solve per test case
}
}
// Pattern 4: R x C grid of integers
// Input:
// 3 4
// 1 2 3 4
// 5 6 7 8
// 9 0 1 2
func pattern4() {
var r, c int
fmt.Fscan(in, &r, &c)
grid := make([][]int, r)
for i := range grid {
grid[i] = make([]int, c)
for j := range grid[i] { fmt.Fscan(in, &grid[i][j]) }
}
for _, row := range grid { fmt.Fprintln(out, row) }
}
// Pattern 4b: R x C grid of characters (maze, board)
// Input:
// 3 3
// #.#
// .S.
// #.#
func pattern4b() {
var r, c int
fmt.Fscan(in, &r, &c)
scanner := bufio.NewScanner(in)
scanner.Scan() // consume trailing newline after "r c"
grid := make([]string, r)
for i := range grid {
scanner.Scan()
grid[i] = scanner.Text()
}
for _, row := range grid { fmt.Fprintln(out, row) }
}
// Pattern 5: Graph as edge list
// Input:
// 4 5 ← n nodes, m edges
// 0 1
// 0 2
// 1 3
// 2 3
// 1 2
func pattern5() {
var n, m int
fmt.Fscan(in, &n, &m)
adj := make([][]int, n)
for i := 0; i < m; i++ {
var u, v int
fmt.Fscan(in, &u, &v)
adj[u] = append(adj[u], v)
adj[v] = append(adj[v], u) // undirected; remove for directed
}
for i, neighbors := range adj {
fmt.Fprintf(out, "%d: %v\n", i, neighbors)
}
}
// Pattern 6: Read words / string tokens per line
// Input:
// hello world foo
// bar baz
func pattern6() {
scanner := bufio.NewScanner(in)
for scanner.Scan() {
words := strings.Fields(scanner.Text())
fmt.Fprintln(out, len(words), words)
}
}
Used in real CLI tools, not competitive problems. Know this for system design / platform engineering rounds where you're building a tool.
package main
import (
"flag"
"fmt"
"os"
"strconv"
)
func main() {
// ── os.Args — raw positional arguments ───────────────
// Usage: go run cli_args.go hello 42
fmt.Println("program:", os.Args[0])
if len(os.Args) > 1 {
fmt.Println("args:", os.Args[1:])
for i, arg := range os.Args[1:] {
if n, err := strconv.Atoi(arg); err == nil {
fmt.Printf(" arg[%d] is int: %d\n", i, n)
} else {
fmt.Printf(" arg[%d] is string: %s\n", i, arg)
}
}
}
// ── flag package — named flags ────────────────────────
// Usage: go run cli_args.go -port 8080 -verbose -name gopher
port := flag.Int("port", 3000, "server port")
verbose := flag.Bool("verbose", false, "enable verbose output")
name := flag.String("name", "world", "name to greet")
timeout := flag.Duration("timeout", 0, "request timeout e.g. 30s")
flag.Parse() // MUST call before reading any flag values
// flag.Args() returns remaining non-flag arguments
rest := flag.Args()
fmt.Printf("port=%d verbose=%v name=%s timeout=%v\n",
*port, *verbose, *name, *timeout)
fmt.Println("remaining args:", rest)
// Custom usage message
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
flag.PrintDefaults()
}
}
# Pipe a string directly
echo "5\n3 1 4 1 5" | go run solution.go
# Multi-line input (use $'...' for actual newlines in bash)
echo $'5\n3 1 4 1 5' | go run solution.go
# Redirect from a file (best for complex test cases)
go run solution.go < input.txt
# Redirect output too
go run solution.go < input.txt > output.txt
# Interactive: just run and type, Ctrl+D to send EOF
go run solution.go
# Create a test input file
cat > input.txt << 'EOF'
5
3 1 4 1 5
EOF
go run solution.go < input.txt
os.Stdin by piping through a strings.Reader:
old := os.Stdin; r, w, _ := os.Pipe(); os.Stdin = r; w.WriteString("5\n"); w.Close(); // run; os.Stdin = old
fmt — Format Verbs
Go's formatted I/O package — analogous to C's stdio.h. Provides printing (Print/Println/Printf), string formatting (Sprintf), scanning (Scan/Scanf), and error wrapping (Errorf with %w).
A printing press with templates. The format string is the template with blank slots (%d, %s); the arguments fill in the slots. %v is the universal slot — it uses the value's default format, like a template that just writes whatever you hand it.
%v uses reflection to format any type. Printf writes to stdout (os.Stdout writer). Fprintf writes to any io.Writer — use this for buffered output. Errorf with %w wraps an error, preserving the chain for errors.Is and errors.As unwrapping.
%+v for structs with field names (debugging). %T to print a value's type. %w in fmt.Errorf for wrapping errors with context. fmt.Fprintln(writer, ...) instead of fmt.Println for competitive programming with buffered output.
| Verb | Meaning | Example |
|---|---|---|
%v | Default format | fmt.Sprintf("%v", []int{1,2,3}) → [1 2 3] |
%+v | Struct with field names | fmt.Sprintf("%+v", p) → {Name:Alice Age:30} |
%#v | Go syntax representation | main.Person{Name:"Alice"} |
%T | Type | fmt.Sprintf("%T", 42) → int |
%d | Integer decimal | %05d → zero-padded |
%b | Binary | fmt.Sprintf("%b", 6) → 110 |
%x | Hex lowercase | fmt.Sprintf("%x", 255) → ff |
%f | Float | %.2f → 2 decimal places |
%s | String | |
%q | Quoted string | "hello" |
%p | Pointer address | |
%w | Wrap error (fmt.Errorf only) | fmt.Errorf("db: %w", err) |
strings & strconv
strings: operations on immutable string values (split, join, replace, trim, search). strconv: conversions between strings and other primitive types (int, float, bool). Together they cover virtually all string processing needs in Go.
strings is a text editor's toolbar — cut, copy, find, replace, capitalise. strconv is the "Save As" dialog — it changes the file format (type), not the content: "42" (string) ↔ 42 (int), "3.14" ↔ 3.14, "true" ↔ true.
Most strings functions return new strings (immutable). strings.Builder uses an internal []byte and a WriteString method — O(1) amortised per append, O(n) total. strconv.Atoi parses base-10; use ParseInt(s, base, bitSize) for hex/binary. FormatInt(n, base) for the reverse.
strings.Fields to split on any whitespace (competitive input parsing). strings.Builder for building strings in loops. strconv.Atoi/Itoa for int↔string. strconv.ParseInt(s, 16, 64) for hex parsing. Never string(65) for int→string — use Itoa.
package main
import (
"fmt"
"strings"
"strconv"
)
func main() {
s := " Hello, World! "
// ── strings package ──────────────────────────────────
fmt.Println(strings.TrimSpace(s)) // "Hello, World!"
fmt.Println(strings.ToLower("ABC")) // "abc"
fmt.Println(strings.ToUpper("abc")) // "ABC"
fmt.Println(strings.Contains("hello", "ell")) // true
fmt.Println(strings.HasPrefix("hello", "he")) // true
fmt.Println(strings.HasSuffix("hello", "lo")) // true
fmt.Println(strings.Index("hello", "ll")) // 2 (-1 if not found)
fmt.Println(strings.Count("hello", "l")) // 2
fmt.Println(strings.Replace("oink oink", "oink", "moo", 1)) // "moo oink"
fmt.Println(strings.ReplaceAll("aaa", "a", "b")) // "bbb"
parts := strings.Split("a,b,c", ",") // ["a" "b" "c"]
fmt.Println(strings.Join(parts, "-")) // "a-b-c"
fmt.Println(strings.Fields(" foo bar baz ")) // ["foo" "bar" "baz"]
// Efficient concatenation — O(n), not O(n²)
var sb strings.Builder
for i := 0; i < 5; i++ { fmt.Fprintf(&sb, "%d", i) }
fmt.Println(sb.String()) // "01234"
// strings.NewReader for byte-by-byte processing
r := strings.NewReader("Hello")
fmt.Println(r.Len()) // 5
// ── strconv package ──────────────────────────────────
// int ↔ string
n, err := strconv.Atoi("123")
fmt.Println(n, err) // 123
_, err = strconv.Atoi("abc")
fmt.Println(err) // invalid syntax
fmt.Println(strconv.Itoa(42)) // "42"
// ParseInt(s, base, bitSize)
i64, _ := strconv.ParseInt("FF", 16, 64) // hex
fmt.Println(i64) // 255
i64, _ = strconv.ParseInt("1010", 2, 64) // binary
fmt.Println(i64) // 10
// FormatInt(n, base)
fmt.Println(strconv.FormatInt(255, 16)) // "ff"
fmt.Println(strconv.FormatInt(255, 2)) // "11111111"
// float
f, _ := strconv.ParseFloat("3.14", 64)
fmt.Println(f)
fmt.Println(strconv.FormatFloat(f, 'f', 2, 64)) // "3.14"
// bool
b, _ := strconv.ParseBool("true")
fmt.Println(b) // true
}
sort & container/heap
sort: in-place sorting with comparison closures, binary search on sorted slices, and checked sort predicates. container/heap: heap operations (push/pop/fix) on any type implementing the 5-method heap.Interface.
sort is a personal organiser that rearranges items by any rule you specify. container/heap is an always-sorted priority inbox — every time you add or remove an item, the inbox instantly re-sorts so the most important item is always on top.
sort.Slice uses pdqsort (pattern-defeating quicksort) — O(n log n) average and worst case. sort.Search is binary search over an index range with a monotonic predicate — returns leftmost index where predicate is true. heap.Push appends then sifts up; heap.Pop swaps root with last, shrinks, sifts down.
sort.Slice for custom struct ordering. sort.Ints/Strings for primitives. sort.Search for lower/upper bound on sorted data. container/heap whenever you need a priority queue with arbitrary struct ordering — flip Less to toggle min/max heap.
package main
import (
"fmt"
"sort"
)
func main() {
// Primitive slice sorting
ints := []int{5, 2, 8, 1, 9, 3}
sort.Ints(ints) // ascending in-place
fmt.Println(ints) // [1 2 3 5 8 9]
sort.Sort(sort.Reverse(sort.IntSlice(ints))) // descending
fmt.Println(ints) // [9 8 5 3 2 1]
strs := []string{"banana", "apple", "cherry"}
sort.Strings(strs)
fmt.Println(strs) // [apple banana cherry]
// Custom sort with sort.Slice (unstable) / sort.SliceStable
type Person struct{ Name string; Age int }
people := []Person{{"Bob", 25}, {"Alice", 30}, {"Charlie", 25}}
// Sort by age asc, then name asc
sort.Slice(people, func(i, j int) bool {
if people[i].Age != people[j].Age {
return people[i].Age < people[j].Age
}
return people[i].Name < people[j].Name
})
fmt.Println(people) // [{Bob 25} {Charlie 25} {Alice 30}]
// Binary search (on sorted slice)
sorted := []int{1, 3, 5, 7, 9}
idx := sort.SearchInts(sorted, 5) // returns index where 5 is/would be
fmt.Println(idx, sorted[idx] == 5) // 2 true
// sort.Search — binary search with predicate
// "find first i in [0,n) where f(i) is true"
n := sort.Search(len(sorted), func(i int) bool { return sorted[i] >= 6 })
fmt.Println(n) // 3 (first index with value >= 6)
// Check if sorted
fmt.Println(sort.IntsAreSorted(sorted)) // true
// 2D sort — sort rows by first element
matrix := [][]int{{3,1},{1,4},{2,2},{1,3}}
sort.Slice(matrix, func(i, j int) bool {
if matrix[i][0] != matrix[j][0] { return matrix[i][0] < matrix[j][0] }
return matrix[i][1] < matrix[j][1]
})
fmt.Println(matrix) // [[1 3] [1 4] [2 2] [3 1]]
}
math, errors & Fast I/O
math: floating-point functions and constants (sqrt, pow, ceil, floor, min, max, Inf, MaxInt64). math/bits: CPU-level bit manipulation. errors: error creation and chain unwrapping. bufio: buffered I/O for performance-critical reading/writing.
A scientific calculator (math), a binary wrench for hardware-level tightening (bits), an error message decoder ring (errors), and a loading dock with a staging area (bufio — batches work instead of making a trip per item).
math.MaxInt64 is a compile-time constant. bits.OnesCount compiles to a single CPU POPCNT instruction. errors.Is walks the error chain via Unwrap(). bufio.Writer accumulates writes in a 4KB buffer — Flush() commits to the underlying writer in one syscall.
math.MaxInt64 / 1<<62 as infinity sentinel in graph algorithms. bits.OnesCount for popcount in bitmask DP. errors.Is(err, target) to check sentinel errors across wrapping layers. bufio always for competitive programming output — and never forget defer writer.Flush().
package main
import (
"bufio"
"errors"
"fmt"
"math"
"math/bits"
"os"
)
func main() {
// ── math ─────────────────────────────────────────────
fmt.Println(math.MaxInt64) // 9223372036854775807
fmt.Println(math.MinInt64) // -9223372036854775808
fmt.Println(math.MaxFloat64)
fmt.Println(math.Abs(-3.14)) // 3.14
fmt.Println(math.Sqrt(16)) // 4
fmt.Println(math.Pow(2, 10)) // 1024
fmt.Println(math.Log2(1024)) // 10
fmt.Println(math.Ceil(3.1)) // 4
fmt.Println(math.Floor(3.9)) // 3
fmt.Println(math.Min(3.0, 5.0)) // 3
fmt.Println(math.Max(3.0, 5.0)) // 5
// Integer min/max — Go 1.21+
fmt.Println(min(3, 5)) // builtin since 1.21
fmt.Println(max(3, 5))
// Inf and NaN
inf := math.Inf(1) // +Inf
fmt.Println(math.IsInf(inf, 1)) // true
// ── bits package (bit manipulation) ──────────────────
fmt.Println(bits.OnesCount(6)) // 2 (binary: 110)
fmt.Println(bits.Len(255)) // 8
fmt.Println(bits.TrailingZeros(8)) // 3
// Common bit tricks
x := 13 // 1101
fmt.Println(x & (x-1)) // 1100 — clear lowest set bit
fmt.Println(x & -x) // 0001 — isolate lowest set bit
fmt.Println(x | (x+1)) // 1111 — set lowest unset bit
isPow2 := x > 0 && x & (x-1) == 0
fmt.Println("isPow2:", isPow2) // false
// ── Error handling ────────────────────────────────────
sentinel := errors.New("not found")
wrapped := fmt.Errorf("db query: %w", sentinel)
fmt.Println(errors.Is(wrapped, sentinel)) // true — unwraps chain
var target *os.PathError
fmt.Println(errors.As(wrapped, &target)) // false — wrong type
// ── Fast I/O (competitive / large input) ─────────────
reader := bufio.NewReader(os.Stdin)
writer := bufio.NewWriter(os.Stdout)
defer writer.Flush() // MUST flush buffered writer
// Read line
// line, _ := reader.ReadString('\n')
// line = strings.TrimRight(line, "\r\n")
// Scan integers fast
var n int
fmt.Fscan(reader, &n) // reads from buffered reader
_ = n
// Print fast
fmt.Fprintln(writer, "result")
// ── Modular arithmetic (common in DP/combinatorics) ──
const MOD = 1_000_000_007
a, b := 999_999_999, 999_999_999
product := (a % MOD * (b % MOD)) % MOD
fmt.Println(product)
}
const INF = int(^uint(0) >> 1) for MaxInt, and -INF for MinInt. Or define func min(a, b int) int manually.
int or int64 over int32. (5) Use named variables over clever one-liners — readability matters at Staff level.