Interview Prep Hub

Data Structures & Algorithms

Most interviews include 1–2 coding rounds. You don't need to memorize Leetcode Hards — mastering the core patterns for Mediums is usually enough.

Big-O Cheat Sheet

Data StructureAccessSearchInsertionDeletion
Array / SliceO(1)O(n)O(n)O(n)
Hash Map / SetO(1)O(1)O(1)
Binary Search TreeO(log n)O(log n)O(log n)O(log n)
Linked ListO(n)O(n)O(1)O(1)
Min/Max HeapO(1) (Min/Max)O(n)O(log n)O(log n)

Must-Know Algorithm Patterns

1. Sliding Window

Used for finding subarrays or substrings that satisfy a condition (e.g. "Longest substring without repeating characters").

// Template: Variable Sliding Window
left := 0
for right := 0; right < len(arr); right++ {
    // 1. Add arr[right] to window state
    // 2. While window is invalid:
    //      Remove arr[left] from window state
    //      left++
    // 3. Update max/min result
}
2. Two Pointers

Used for sorted arrays or linked lists. (e.g. "Two Sum II", "Valid Palindrome", "Container With Most Water").

left, right := 0, len(arr)-1
for left < right {
    sum := arr[left] + arr[right]
    if sum == target { return true }
    if sum < target { left++ } else { right-- }
}
3. Fast & Slow Pointers

Used for cycle detection in Linked Lists or Arrays (Floyd's Tortoise and Hare). Find middle of linked list.

slow, fast := head, head
for fast != nil && fast.Next != nil {
    slow = slow.Next
    fast = fast.Next.Next
    if slow == fast { return true } // Cycle detected
}
4. Binary Search

Not just for sorted arrays! Used anytime the "answer space" is monotonic (e.g. Koko Eating Bananas).

left, right := 0, len(arr)-1
for left <= right {
    mid := left + (right-left)/2
    if arr[mid] == target { return mid }
    if arr[mid] < target { left = mid + 1 } else { right = mid - 1 }
}
5. BFS / DFS on Matrices (Islands)

Graph traversal on 2D grids (Number of Islands, Rotten Oranges).

// DFS Template for Grid
func dfs(grid [][]byte, r, c int) {
    if r < 0 || c < 0 || r >= len(grid) || c >= len(grid[0]) || grid[r][c] == '0' { return }
    grid[r][c] = '0' // Mark visited
    dfs(grid, r+1, c); dfs(grid, r-1, c); dfs(grid, r, c+1); dfs(grid, r, c-1)
}
6. Top K Elements (Heaps)

Anytime you see "Top K", "Kth largest", or "K closest", use a Heap (Priority Queue). A Min-Heap of size K keeps the K largest elements (since the smallest of the largest is at the top, ready to be popped).

7. Dynamic Programming

Optimizing recursion by storing intermediate results. Two approaches:

  • Top-Down (Memoization): Recursion + HashMap to cache results. Easiest to write.
  • Bottom-Up (Tabulation): Iteration + Array. Better space complexity (can often optimize to O(1) space, e.g. Fibonacci only needs last 2 variables).

Go-Specific Tips for Coding Rounds

  • HashMaps: map[string]int. Note that Go maps do NOT maintain insertion order.
  • Sets: Go doesn't have a built-in Set. Use a map with empty structs to save memory: map[string]struct{}.
  • Strings: Strings are immutable. Convert to []rune to manipulate characters, especially for Unicode. Use strings.Builder for efficient concatenation.
  • Sorting: sort.Ints(arr), sort.Strings(arr). For custom sorting, use sort.Slice(arr, func(i, j int) bool { return arr[i] &lt; arr[j] }).
  • Heaps: You must implement the heap.Interface (Len, Less, Swap, Push, Pop) on a slice type. Memorize this boilerplate before interviews.
  • Queues/Stacks: Just use slices. Push: stack = append(stack, val). Pop: val, stack = stack[len(stack)-1], stack[:len(stack)-1]. Dequeue (BFS): val, q = q[0], q[1:].

Additional Must-Know Patterns

8. Graph Algorithms (BFS/DFS on Adjacency Lists)

Many problems are graphs in disguise (prerequisites, social networks, routes).

// Build adjacency list
graph := make(map[int][]int)
for _, edge := range edges {
    graph[edge[0]] = append(graph[edge[0]], edge[1])
    graph[edge[1]] = append(graph[edge[1]], edge[0]) // undirected
}

// BFS — shortest path in unweighted graph
func bfs(graph map[int][]int, start int) map[int]int {
    dist := map[int]int{start: 0}
    queue := []int{start}
    for len(queue) > 0 {
        node := queue[0]
        queue = queue[1:]
        for _, neighbor := range graph[node] {
            if _, visited := dist[neighbor]; !visited {
                dist[neighbor] = dist[node] + 1
                queue = append(queue, neighbor)
            }
        }
    }
    return dist
}

// Topological Sort (for DAGs — course prerequisites, build order)
// Using Kahn's algorithm (BFS-based)
func topologicalSort(numCourses int, prerequisites [][]int) []int {
    inDegree := make([]int, numCourses)
    graph := make(map[int][]int)
    for _, p := range prerequisites {
        graph[p[1]] = append(graph[p[1]], p[0])
        inDegree[p[0]]++
    }
    
    queue := []int{}
    for i, d := range inDegree {
        if d == 0 { queue = append(queue, i) }
    }
    
    var order []int
    for len(queue) > 0 {
        node := queue[0]; queue = queue[1:]
        order = append(order, node)
        for _, next := range graph[node] {
            inDegree[next]--
            if inDegree[next] == 0 { queue = append(queue, next) }
        }
    }
    if len(order) != numCourses { return nil } // cycle detected
    return order
}
9. Backtracking

Generate all combinations, permutations, or subsets. Think of it as DFS on a decision tree.

// Template: Generate all subsets
func subsets(nums []int) [][]int {
    var result [][]int
    var current []int
    
    var backtrack func(start int)
    backtrack = func(start int) {
        // Make a copy of current and add to result
        temp := make([]int, len(current))
        copy(temp, current)
        result = append(result, temp)
        
        for i := start; i < len(nums); i++ {
            current = append(current, nums[i])  // choose
            backtrack(i + 1)                     // explore
            current = current[:len(current)-1]   // un-choose (backtrack)
        }
    }
    
    backtrack(0)
    return result
}
// Key insight: "choose, explore, un-choose"
10. Monotonic Stack

Find the "next greater/smaller element" in O(n). Stack maintains monotonic order.

// 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[stack[len(stack)-1]] < nums[i] {
            idx := stack[len(stack)-1]
            stack = stack[:len(stack)-1]
            result[idx] = nums[i]
        }
        stack = append(stack, i)
    }
    return result
}
// Also used in: Largest Rectangle in Histogram, Daily Temperatures
11. Trie (Prefix Tree)

Efficient storage and retrieval of strings. Used for autocomplete, spell check, IP routing.

type TrieNode struct {
    children map[rune]*TrieNode
    isEnd    bool
}

type Trie struct {
    root *TrieNode
}

func NewTrie() *Trie {
    return &Trie{root: &TrieNode{children: make(map[rune]*TrieNode)}}
}

func (t *Trie) Insert(word string) {
    node := t.root
    for _, ch := range word {
        if _, ok := node.children[ch]; !ok {
            node.children[ch] = &TrieNode{children: make(map[rune]*TrieNode)}
        }
        node = node.children[ch]
    }
    node.isEnd = true
}

func (t *Trie) Search(word string) bool {
    node := t.root
    for _, ch := range word {
        if _, ok := node.children[ch]; !ok { return false }
        node = node.children[ch]
    }
    return node.isEnd
}

func (t *Trie) StartsWith(prefix string) bool {
    node := t.root
    for _, ch := range prefix {
        if _, ok := node.children[ch]; !ok { return false }
        node = node.children[ch]
    }
    return true
}
12. Union-Find (Disjoint Set Union)

Track connected components. Used for: Number of Islands, Graph connectivity, Kruskal's MST.

type UnionFind struct {
    parent []int
    rank   []int
}

func NewUnionFind(n int) *UnionFind {
    parent := make([]int, n)
    rank := make([]int, n)
    for i := range parent { parent[i] = i }
    return &UnionFind{parent, rank}
}

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) bool {
    px, py := uf.Find(x), uf.Find(y)
    if px == py { return false } // already connected
    // Union by rank
    if uf.rank[px] < uf.rank[py] { px, py = py, px }
    uf.parent[py] = px
    if uf.rank[px] == uf.rank[py] { uf.rank[px]++ }
    return true
}

Complexity Analysis Tips

ComplexityNameExample
O(1)ConstantHashMap lookup, array access by index
O(log n)LogarithmicBinary search, balanced BST operations
O(n)LinearSingle pass through array, linear search
O(n log n)LinearithmicMerge sort, heap sort, sorting in general
O(n²)QuadraticBubble sort, nested loops, brute force pairs
O(2ⁿ)ExponentialSubsets, recursive Fibonacci without memo
O(n!)FactorialPermutations, traveling salesman brute force

Space complexity matters too: Recursive DFS uses O(height) stack space. BFS uses O(width) queue space. Mention both in interviews.

Problem Solving Framework

  1. Clarify: Ask about constraints, edge cases, input size. "Can the array contain negatives? Duplicates? Is it sorted?"
  2. Examples: Walk through 2-3 examples including edge cases (empty input, single element).
  3. Brute Force: State the obvious O(n²) or O(n!) solution first. It shows you understand the problem.
  4. Optimize: Identify the pattern. Can you use a HashMap to trade space for time? Sorting? Two pointers?
  5. Code: Write clean code. Use meaningful variable names. Handle edge cases first.
  6. Test: Trace through your code with your examples. Check off-by-one errors.
  7. Analyze: State time and space complexity.

Interview Quick Reference

PatternRecognize WhenClassic Problems
Sliding Window"Subarray/substring" with a conditionLongest Substring Without Repeating, Minimum Window Substring
Two PointersSorted array, pairs, palindromesTwo Sum II, Container With Most Water, Valid Palindrome
Binary SearchSorted data or monotonic answer spaceSearch in Rotated Array, Koko Eating Bananas
BFS/DFSGraphs, trees, grids, connected componentsNumber of Islands, Word Ladder, Clone Graph
Topological SortDAGs, dependencies, orderingCourse Schedule, Build Order
Heap / Top K"Kth largest", "top K", "K closest"Kth Largest Element, Merge K Sorted Lists
Dynamic ProgrammingOverlapping subproblems + optimal substructureClimbing Stairs, Coin Change, Longest Common Subsequence
BacktrackingGenerate all combinations/permutationsSubsets, Permutations, N-Queens
Union-FindConnected components, group membershipNumber of Provinces, Redundant Connection