Data Structures Interview Questions (2026)

The 65 data structure and algorithm questions interviewers actually ask, with direct answers, runnable code, Big-O tables, and what the interviewer is listening for. Grouped for freshers, intermediate, and experienced rounds.

65 questions with answers

What Are Data Structures?

Key Takeaways

  • A data The technical sequence is a way of organizing data in memory so specific operations (search, insert, delete, order) run efficiently. The right choice can turn a slow function into an instant one.
  • Interviews test whether you can pick the correct structure for a problem and justify it with time and space complexity, not whether you memorized code.
  • You can answer coding rounds in whatever language you're strongest in. This page uses Python because it's readable, but the structures and Big-O costs are the same in Java, C++, or JavaScript.
  • This page is a question bank: work your tier first, read one tier up, and say answers out loud, because how you reason about trade-offs, not just the final code is the technical point.

A data The technical sequence is a way of storing and organizing data so that the operations you care about run efficiently. An array gives instant access by index. A hash table gives near-instant lookup by key. A tree keeps data sorted while still allowing fast search. The structure you choose decides whether a feature runs in a millisecond or a full second, which is why data structure questions dominate technical interviews. Algorithms are the procedures that run on those structures: searching, sorting, and traversing. Together they're called DSA, data structures and algorithms. Interviewers care less about whether you've memorized a red-black tree and more about whether you can look at a problem, The structure that fits, and defend the choice with Big-O reasoning. Most coding rounds let you pick your language, so answer in whichever you know best. This page uses Python for its readability, but every structure and complexity here applies across languages. The official Python tutorial from the Python Software Foundation covers the built-in structures if you want to ground the fundamentals, and since the first technical round is increasingly a live AI coding interview, pair this bank with our AI interview preparation guides for the format side.

65Questions with answers on this page
3Difficulty tiers: freshers, intermediate, experienced
25+Runnable Python snippets you can practice from
45-60 minTypical length of a DSA coding round

Watch: Data Structures Easy to Advanced Course: Full Tutorial from a Google Engineer

Video: Data Structures Easy to Advanced Course: Full Tutorial from a Google Engineer (freeCodeCamp.org, YouTube)

Test yourself and earn a certificate

10 quick questions. Score 70%+ to download your Data Structures certificate.

Jump to quiz

All Questions on This Page

65 questions
Data Structure Interview Questions for Freshers
  1. 1. What is a data structure and why does it matter?
  2. 2. What is the difference between an array and a linked list?
  3. 3. What is Big-O notation and why do interviewers care about it?
  4. 4. What is a stack and where is it used?
  5. 5. What is a queue and how does it differ from a stack?
  6. 6. What is a hash table and how does it achieve O(1) lookup?
  7. 7. What is a hash collision and how is it resolved?
  8. 8. What is the load factor of a hash table and why does it trigger resizing?
  9. 9. When should you use a set instead of a list?
  10. 10. What is a binary tree and what is a binary search tree?
  11. 11. What are the tree traversal orders and when is each used?
  12. 12. What is a heap and how does it relate to a priority queue?
  13. 13. How do you represent a graph in code?
  14. 14. What is the difference between BFS and DFS?
  15. 15. How does binary search work and what is its complexity?
  16. 16. What is recursion and what does every recursive function need?
  17. 17. How do you reverse a singly linked list?
  18. 18. What are the time complexities of common array operations?
  19. 19. How are strings handled as a data structure, and why does immutability matter?
  20. 20. When would you use a hash table instead of an array?
  21. 21. What is a circular buffer and when is it useful?
  22. 22. What is the difference between an abstract data type and a data structure?
  23. 23. Why do balanced trees like AVL and red-black trees exist?
  24. 24. What is space complexity and how is it different from time complexity?
  25. 25. What is a deque and how does it differ from a stack and a queue?
Data Structures Intermediate Interview Questions
  1. 26. Compare quicksort, merge sort, and heap sort. When do you pick each?
  2. 27. What does it mean for a sort to be stable, and when does it matter?
  3. 28. How do you design a stack that returns its minimum in O(1)?
  4. 29. What is the two-pointer technique and when do you use it?
  5. 30. What is the sliding window technique?
  6. 31. How do you detect a cycle in a graph?
  7. 32. What is topological sort and where is it used?
  8. 33. What is a trie and what problem does it solve better than a hash table?
  9. 34. What is memoization and how does it relate to dynamic programming?
  10. 35. Walk through the ways to compute Fibonacci and their complexities.
  11. 36. How would you solve the coin change problem?
  12. 37. When should you use recursion versus iteration?
  13. 38. How do you detect a cycle in a linked list?
  14. 39. When would you choose a balanced BST over a hash table?
  15. 40. What does it mean for an algorithm to be in-place, and why does it matter?
  16. 41. How do you find the shortest path in a weighted graph?
  17. 42. What is amortized analysis? Give an example.
  18. 43. How would you design an LRU cache with O(1) operations?
  19. 44. How do you validate that a binary tree is a valid BST?
  20. 45. What is backtracking and how does it differ from brute force?
  21. 46. What are common bit manipulation tricks worth knowing?
  22. 47. What is a prefix sum array and what problem does it optimize?
Data Structure Interview Questions for Experienced Developers
  1. 48. How do you choose a data structure for a system that must scale?
  2. 49. Why do databases use B-trees instead of binary search trees?
  3. 50. What is a bloom filter and what trade-off does it make?
  4. 51. What is consistent hashing and what problem does it solve?
  5. 52. What is the union-find (disjoint set) structure and where is it used?
  6. 53. What is an LSM-tree and why do write-heavy databases use it?
  7. 54. What is a skip list and why might you use one over a balanced tree?
  8. 55. How does CPU cache behavior affect data structure choice?
  9. 56. What makes designing concurrent data structures hard?
  10. 57. What is a count-min sketch and when would you reach for it?
  11. 58. What are persistent (immutable) data structures and how do they stay efficient?
  12. 59. How do you sort data that doesn't fit in memory?
  13. 60. How would you process a graph too large to fit in memory?
  14. 61. What is a rolling hash and where is it used?
  15. 62. What is a monotonic stack and what class of problems does it solve?
  16. 63. What is a segment tree and when is it worth the complexity?
  17. 64. A senior interviewer says 'design a structure for autocomplete on 10 million entries.' How do you approach it?
  18. 65. When does amortized O(1) become a problem, and how do you fix it?

Data Structure Interview Questions for Freshers

Freshers25 questions

The fundamentals every entry-level round checks. If any answer here surprises you, that's your study list.

Q1. What is a data structure and why does it matter?

A data The technical sequence is a way of organizing data in memory so that specific operations run efficiently. The same data stored as an array, a hash table, or a tree gives completely different speeds for search, insert, and delete.

It matters because the structure you pick decides whether a feature is fast or slow. Looking up a user by ID in a list is O(n); in a hash table it's O(1). Choosing well is often the difference between a millisecond and a second, which is why interviews lead with this.

Key point: Open with one crisp definition plus one concrete speed contrast (list lookup vs hash lookup). Interviewers use this to hear whether you structures connects to real performance.

Q2. What is the difference between an array and a linked list?

An array stores elements in one contiguous block of memory, so accessing element i is O(1) by direct address math. But inserting or deleting in the middle is O(n) because every later element shifts.

A linked list stores each element in a node that points to the next one. There's no contiguous block, so access by position is O(n), but inserting or deleting at a known node is O(1) because you just relink pointers. Arrays also iterate faster in practice because contiguous memory is cache-friendly.

python
# Python's list is a dynamic array
arr = [10, 20, 30]
arr[1]            # O(1) access
arr.insert(1, 15) # O(n): shifts 20 and 30 right

# A simple singly linked list node
class Node:
    def __init__(self, value, nxt=None):
        self.value = value
        self.next = nxt

head = Node(10, Node(20, Node(30)))  # 10 -> 20 -> 30
ArrayLinked list
Access by indexO(1)O(n)
Insert/delete at known nodeO(n)O(1)
Memory layoutContiguousScattered nodes + pointers
Cache friendlinessHighLow

Key point: The follow-up is almost always 'when would you choose a linked list?'. Have the answer ready: frequent insert or delete at positions you already hold a reference to, like an LRU cache.

Q3. What is Big-O notation and why do interviewers care about it?

Big-O describes how an algorithm's running time or memory grows as the input size grows, ignoring constants and lower-order terms. It answers 'does this still work when n gets large?' rather than 'how many milliseconds?'.

The common ladder from fast to slow is O(1) constant, O(log n) logarithmic, O(n) linear, O(n log n), O(n squared) quadratic, and O(2^n) exponential. Interviewers care because it's the shared language for comparing solutions: naming your solution's Big-O and improving it is the core skill a coding round measures.

How fast each complexity grows

Operations for n = 1000 on a log scale. Each step up the ladder is dramatically more work.

O(1) constant
1 operations
O(log n)
10 operations
O(n) linear
1,000 operations
O(n squared)
1,000,000 operations
  • O(1) constant: Same cost no matter the input size, like a hash lookup
  • O(log n): Halves the problem each step, like binary search
  • O(n) linear: One pass over the input, like a single loop
  • O(n squared): Nested loops over the input, avoid at scale

Key point: State both time and space complexity for your solution in practice. Forgetting space is a common miss, and it matters.

Watch a deeper explanation

Video: Big-O notation in 5 minutes (Michael Sambol, YouTube)

Q4. What is a stack and where is it used?

A stack is a Last-In-First-Out (LIFO) structure: the last item pushed is the first popped. It supports push, pop, and peek, all O(1). Think of a stack of plates: you take from the top.

Stacks appear everywhere: the call stack that tracks function calls, undo/redo features, matching brackets in a parser, and depth-first traversal. When a problem needs 'the most recent unmatched thing,' a stack is usually the answer.

python
stack = []
stack.append(1)   # push
stack.append(2)   # push
stack.pop()       # 2, pop the top
stack[-1]         # 1, peek without removing

Key point: Bracket matching is the classic stack coding question. If you can explain that pattern, you've shown you understand LIFO in practice.

Q5. What is a queue and how does it differ from a stack?

A queue is First-In-First-Out (FIFO): the first item enqueued is the first dequeued, like a line at a checkout. A stack is LIFO. That single difference decides which one fits a problem.

Use a deque (double-ended queue) for an efficient queue in Python, because popping from the front of a plain list is O(n). Queues drive breadth-first search, task scheduling, and any 'process in arrival order' workflow.

python
from collections import deque

q = deque()
q.append(1)      # enqueue
q.append(2)
q.popleft()      # 1, dequeue from the front, O(1)

Key point: Mentioning that list.pop(0) is O(n) and deque.popleft() is O(1) shows you know the practical cost, not just the concept.

Q6. What is a hash table and how does it achieve O(1) lookup?

A hash table stores key-value pairs and gives average O(1) lookup, insert, and delete. It runs each key through a hash function that maps it to a slot in an underlying array, so finding a key means computing its hash and going straight to that slot, no scanning.

In Python the built-in dict and set are hash tables. The O(1) is an average: with a good hash function and a reasonable load factor, keys spread out evenly. Bad hashing or too many collisions can degrade it toward O(n).

python
phone = {"Asha": "555-0100"}
phone["Ben"] = "555-0111"   # O(1) insert
phone.get("Asha")           # O(1) lookup
"Ben" in phone              # O(1) membership

Key point: The natural follow-up is 'what happens on a collision?'. Being ready with chaining and open addressing turns a basic answer into a strong one.

Watch a deeper explanation

Video: Data Structures: Hash Tables (HackerRank, YouTube)

Q7. What is a hash collision and how is it resolved?

A collision happens when two different keys hash to the same slot. It's unavoidable because there are more possible keys than slots, so every real hash table needs a resolution strategy.

Two main strategies: separate chaining stores a small list (or tree) of entries at each slot, so colliding keys live together and you scan that short list. Open addressing (used by CPython's dict) probes for the next free slot instead. Both keep average operations O(1) as long as the load factor stays controlled.

Key point: Don't just The strategies, say why collisions are inevitable (pigeonhole principle: more keys than slots). That reasoning is what the technical value is.

Q8. What is the load factor of a hash table and why does it trigger resizing?

The load factor is the ratio of stored entries to available slots (n / capacity). As it climbs, collisions become more frequent and lookups slow down, because more keys share slots.

When the load factor crosses a threshold (often around 0.66 to 0.75), the table resizes: it allocates a larger array and rehashes every key into it. That single insert is O(n), but it's rare, so the amortized cost of insertion stays O(1). This is why dict insertion is fast on average despite the occasional expensive resize.

Key point: The word 'amortized' is the one to reach for here. Explaining that occasional O(n) resizes average out to O(1) shows real understanding.

Q9. When should you use a set instead of a list?

Use a set when you need fast membership tests or automatic deduplication and don't care about order or duplicates. Checking 'x in list' is O(n), but 'x in set' is O(1) average, because a set is a hash table.

The single cheapest optimization in many programs is converting a lookup list to a set. Sets also give O(1) add and remove, plus union, intersection, and difference operations that would be loops on a list.

python
seen = set()
for item in stream:
    if item in seen:     # O(1) instead of O(n)
        continue
    seen.add(item)

unique = set([1, 2, 2, 3])  # {1, 2, 3}, dedup for free

Key point: If a coding problem asks 'have I seen this before?', a set is almost always the intended tool. Reaching for it fast indicates fluency.

Q10. What is a binary tree and what is a binary search tree?

A binary tree is a hierarchy where each node has at most two children, a left and a right. It's the base for many structures. A binary search tree (BST) adds one rule: for every node, all values in the left subtree are smaller and all in the right subtree are larger.

That ordering rule makes search, insert, and delete O(log n) in a balanced BST, because each comparison lets you discard half the remaining tree, just like binary search. An unbalanced BST can degrade to O(n), which is why self-balancing trees exist.

python
class TreeNode:
    def __init__(self, val):
        self.val = val
        self.left = None
        self.right = None

def search(node, target):
    if node is None or node.val == target:
        return node
    if target < node.val:
        return search(node.left, target)
    return search(node.right, target)

Key point: Interviewers often ask why a BST beats a sorted array for a dataset that changes. The answer: O(log n) inserts and deletes versus O(n) shifting in an array.

Q11. What are the tree traversal orders and when is each used?

Depth-first traversals visit a subtree fully before moving on: inorder (left, node, right), preorder (node, left, right), and postorder (left, right, node). Breadth-first (level order) visits the tree level by level using a queue.

Inorder on a BST yields sorted values, the most-tested fact. Preorder is used to copy or serialize a tree. Postorder is used to delete a tree or evaluate expressions bottom-up. Level order handles 'process by depth' problems.

python
def inorder(node, out):
    if node:
        inorder(node.left, out)
        out.append(node.val)   # BST -> sorted order
        inorder(node.right, out)
    return out

Key point: Memorize that inorder of a BST is sorted. It shows up constantly, including 'validate a BST' and 'kth smallest element' questions.

Q12. What is a heap and how does it relate to a priority queue?

A binary heap is a complete binary tree stored in an array where every parent is smaller than its children (min-heap) or larger (max-heap). That property means the min or max is always at the root, so peeking it is O(1), while insert and extract are O(log n).

A priority queue is the abstract idea 'always give me the highest-priority item next,' and a heap is the standard way to implement it. Python's heapq module is a min-heap. Use it for scheduling, Dijkstra's algorithm, and 'top k' problems.

python
import heapq

pq = []
heapq.heappush(pq, 5)
heapq.heappush(pq, 1)
heapq.heappush(pq, 3)
heapq.heappop(pq)   # 1, the smallest, O(log n)

Key point: For 'find the k largest elements,' a heap of size k is the expected answer: O(n log k), better than sorting everything at O(n log n).

Q13. How do you represent a graph in code?

The two standard representations are an adjacency list and an adjacency matrix. An adjacency list maps each node to its neighbors, usually a dict of lists, and uses O(V + E) space. An adjacency matrix is a V-by-V grid where cell [i][j] marks an edge, using O(V squared) space.

Prefer an adjacency list for sparse graphs (most real graphs), because it only stores edges that exist. Use a matrix when the graph is dense or when you need O(1) edge-existence checks. The representation you pick affects the Big-O of your traversal.

python
graph = {
    "A": ["B", "C"],
    "B": ["A", "D"],
    "C": ["A"],
    "D": ["B"],
}  # adjacency list, O(V + E) space

Key point: The space trade-off out loud. Choosing an adjacency list for a sparse graph and explaining why is a small signal that indicates experience.

Q14. What is the difference between BFS and DFS?

Breadth-first search (BFS) explores a graph level by level using a queue: it visits all neighbors at the current distance before going deeper. Depth-first search (DFS) goes as deep as possible down one path before backtracking, using a stack or recursion.

BFS finds the shortest path in an unweighted graph, because the first time it reaches a node is via the fewest edges. DFS uses less memory on deep graphs and naturally fits problems like cycle detection, topological sort, and exploring all paths. Both are O(V + E).

python
from collections import deque

def bfs(graph, start):
    visited = {start}
    q = deque([start])
    while q:
        node = q.popleft()
        for nb in graph[node]:
            if nb not in visited:
                visited.add(nb)
                q.append(nb)
    return visited

How BFS explores a graph

1Start
put the start node in a queue and mark it visited
2Dequeue
take the front node and look at its neighbors
3Enqueue neighbors
add each unvisited neighbor to the queue and mark it visited
4Repeat
continue until the queue is empty; first arrival at a node is a shortest unweighted path

Swap the queue for a stack (or recursion) and the same loop becomes DFS: it goes deep before wide.

Key point: The deciding question is usually 'shortest path in an unweighted graph?'. If yes, say BFS. Naming the reason (level-by-level order) seals it.

Watch a deeper explanation

Video: Breadth First Search Algorithm | Shortest Path | Graph Theory (WilliamFiset, YouTube)

Q15. How does binary search work and what is its complexity?

Binary search finds a target in a sorted array by repeatedly halving the search range. It checks the middle element: if it matches, done; if the target is larger, search the right half; if smaller, the left half. Each step throws away half the remaining elements.

That halving gives O(log n) time, dramatically faster than a linear O(n) scan for large inputs. The two hard requirements: the data must be sorted, and you must compute the midpoint without integer overflow (in Python that's a non-issue, but it matters in other languages).

python
def binary_search(arr, target):
    low, high = 0, len(arr) - 1
    while low <= high:
        mid = (low + high) // 2
        if arr[mid] == target:
            return mid
        if arr[mid] < target:
            low = mid + 1
        else:
            high = mid - 1
    return -1  # not found

How binary search narrows the range

1Set bounds
low at index 0, high at the last index of the sorted array
2Check the middle
compute mid, compare the middle value to the target
3Discard half
if target is larger, move low above mid; if smaller, move high below mid
4Repeat
loop until low passes high; each step halves the range, giving O(log n)

Binary search only works on sorted data. On unsorted input it returns wrong answers, not just slow ones.

Key point: The off-by-one on the loop condition and the mid update is the classic bug. Use low <= high and mid +/- 1, and trace a two-element array to prove it terminates.

Watch a deeper explanation

Video: Binary Search Algorithm in 100 Seconds (Fireship, YouTube)

Q16. What is recursion and what does every recursive function need?

Recursion is when a function calls itself to solve a smaller version of the same problem. Every recursive function needs two parts: a base case that stops the recursion, and a recursive case that moves toward the base case.

Without a correct base case, recursion runs until it exhausts the call stack and raises a RecursionError. Recursion shines on naturally nested problems: tree traversals, divide-and-conquer sorts, and anything with self-similar structure. It trades clarity for the memory cost of the call stack.

python
def factorial(n):
    if n <= 1:        # base case
        return 1
    return n * factorial(n - 1)  # recursive case

Key point: Always The base case first when you explain a recursive solution. Interviewers watch specifically for whether you handle termination.

Q17. How do you reverse a singly linked list?

Walk the list once, reversing each node's next pointer to point at the previous node instead. Keep three references: previous (starts as None), current (starts at head), and a temporary to remember the next node before you overwrite the pointer.

It's O(n) time and O(1) space, and it's one of the most common warm-up questions in a coding round because it tests whether you can manipulate pointers carefully without losing the rest of the list.

python
def reverse(head):
    prev = None
    curr = head
    while curr:
        nxt = curr.next   # save next before overwriting
        curr.next = prev  # reverse the pointer
        prev = curr
        curr = nxt
    return prev           # new head

Key point: Saving the next node before reassigning the pointer is the step people forget. Say it out loud as you write it so the interviewer sees you know the trap.

Q18. What are the time complexities of common array operations?

Access by index and updating an element are O(1). Appending to the end of a dynamic array is amortized O(1). Inserting or deleting in the middle is O(n) because elements shift. Searching an unsorted array is O(n); a sorted array supports O(log n) binary search.

Knowing these cold lets you spot when a nested loop over an array is secretly O(n squared), which is the most common performance mistake in coding interviews.

OperationTime complexity
Access / update by indexO(1)
Append to endO(1) amortized
Insert / delete in middleO(n)
Search (unsorted)O(n)
Search (sorted, binary)O(log n)

Key point: When you write a nested loop over an array, say its complexity out loud. Catching your own O(n squared) before the interviewer does is a strong signal.

Q19. How are strings handled as a data structure, and why does immutability matter?

A string is a sequence of characters, essentially an array you can index and slice. In many languages including Python, strings are immutable: any operation that seems to modify a string actually creates a new one.

That immutability matters for performance. Building a large string by repeatedly concatenating with += in a loop is O(n squared) because each step copies the whole string so far. Collect the pieces in a list and join once for O(n). This is a frequent gotcha interviewers plant.

python
parts = []
for row in rows:
    parts.append(str(row))
result = "".join(parts)   # O(n), one allocation

Key point: The join idiom is the expected fix. Volunteering it when a string-building question comes up answers the follow-up before it's asked.

Q20. When would you use a hash table instead of an array?

Use a hash table when your keys aren't small contiguous integers. Arrays index by position, so they're perfect for 'the 5th element,' but useless for 'the record for user email X.' A hash table maps arbitrary hashable keys to values with O(1) average access.

The other trigger is counting and grouping. Counting word frequencies, grouping items by category, or checking 'have I seen this key?' all map cleanly to a dict, where an array would force you to search.

python
from collections import Counter

words = ["a", "b", "a", "c", "b", "a"]
Counter(words)   # {"a": 3, "b": 2, "c": 1}, one pass, O(n)

Key point: Counter and defaultdict are the fluent tools here. Reaching for Counter on a frequency question instead of a manual loop indicates real-world experience.

Q21. What is a circular buffer and when is it useful?

A circular buffer (ring buffer) is a fixed-size array where the end wraps around to the beginning. Two indices track the head and tail, and when they reach the end they wrap to index 0 using modulo. It gives O(1) enqueue and dequeue in constant memory.

It's useful when you want the most recent N items and don't want memory to grow: streaming logs, audio buffers, sliding-window metrics, and producer-consumer queues. Old data is overwritten as new data arrives, which is exactly the behavior you want for a rolling window.

Key point: The key phrase is 'fixed memory, constant-time operations.' If a question mentions 'the last N events' with a memory limit, a ring buffer is the intended answer.

Q22. What is the difference between an abstract data type and a data structure?

An abstract data type (ADT) is a specification: it defines what operations a type supports and their behavior, without saying how they're implemented. A stack ADT promises push, pop, and peek. A data The technical sequence is the concrete implementation that fulfills that contract.

A stack ADT can be built on an array or a linked list; both satisfy the contract with different performance. Separating the two is why interviewers ask 'implement a queue using two stacks': you're implementing one ADT with another structure.

Key point: This distinction sets up the 'implement X using Y' questions. Naming the ADT versus the implementation shows you think about interfaces, not just code.

Q23. Why do balanced trees like AVL and red-black trees exist?

A plain binary search tree gives O(log n) operations only when it stays balanced. Insert sorted data into a naive BST and it degrades into a linked list, making every operation O(n). Balanced trees prevent that.

AVL trees and red-black trees rebalance themselves after each insert or delete using rotations, guaranteeing O(log n) height no matter the input order. Red-black trees are the more common choice in libraries (they rebalance less aggressively), and they back structures like Java's TreeMap and C++'s std::map.

Key point: You rarely have to implement one from scratch in a fresher round. Knowing why they exist (guaranteed O(log n) despite adversarial input) is what's actually tested.

Q24. What is space complexity and how is it different from time complexity?

Space complexity measures how much extra memory an algorithm uses as the input grows, in the same Big-O terms as time. It counts memory you allocate beyond the input: recursion stack frames, a hash set of seen items, a result array.

The two often trade off. You can speed up a repeated computation by caching results (memoization), spending O(n) space to save time. Interviewers ask for both because a solution that's fast but uses O(n squared) memory can be as unusable as a slow one.

Key point: Count the recursion call stack toward space complexity. A recursive tree traversal is O(h) space for the tree's height, a detail candidates often miss.

Q25. What is a deque and how does it differ from a stack and a queue?

A deque (double-ended queue, pronounced 'deck') lets you add and remove from both ends in O(1). A stack only touches one end (LIFO) and a queue adds at one end and removes at the other (FIFO). A deque generalizes both: use one end and it's a stack, use both ends the FIFO way and it's a queue.

In Python, collections.deque is the go-to for both stacks and queues because appending and popping from either end is O(1), while a plain list is O(n) to pop from the front. Deques also power sliding-window problems where you add on the right and drop stale items from the left.

python
from collections import deque

d = deque([2, 3])
d.appendleft(1)   # [1, 2, 3], O(1) at the front
d.append(4)       # [1, 2, 3, 4], O(1) at the back
d.popleft()       # 1, O(1)
d.pop()           # 4, O(1)

Key point: The reason to reach for a deque over a list is the O(1) front operations. Naming that cost difference is what the question is checking.

Back to question list

Data Structures Intermediate Interview Questions

Intermediate22 questions

For candidates with working experience: algorithm trade-offs, common patterns, and the questions that separate people who use structures from those who understand them.

Q26. Compare quicksort, merge sort, and heap sort. When do you pick each?

All three sort in O(n log n) on average, but the trade-offs differ. Quicksort is usually the fastest in O(log n) space, but it's O(n squared) worst case and not stable is the practice path. Merge sort is always O(n log n) and stable, but needs O(n) extra space. Heap sort is always O(n log n) with O(1) space, but it's not stable and has poor cache behavior.

Pick quicksort as the default for in-memory sorting where average speed matters. Pick merge sort when you need stability or you're sorting linked lists or data too big for memory (external sort). Pick heap sort when you need guaranteed O(n log n) with minimal memory and don't need stability.

AlgorithmAverageWorstSpaceStable
QuicksortO(n log n)O(n squared)O(log n)No
Merge sortO(n log n)O(n log n)O(n)Yes
Heap sortO(n log n)O(n log n)O(1)No

Key point: The follow-up is 'why is quicksort O(n squared) in the worst case?'. Answer: bad pivots (smallest or largest each time) on already-sorted input. Randomized or median-of-three pivots avoid it.

Watch a deeper explanation

Video: Quick sort in 4 minutes (Michael Sambol, YouTube)

Q27. What does it mean for a sort to be stable, and when does it matter?

A stable sort preserves the relative order of elements that compare equal. If two records have the same key, they stay in their original order after sorting. Merge sort and Python's Timsort are stable; quicksort and heap sort are not.

It matters for multi-level sorting. To sort employees by department, then by name within each department, you sort by name first, then by department with a stable sort, and the name order survives. Without stability that second sort would scramble the first. Python's sorted() and list.sort() are stable, which is why this pattern works.

python
rows = [("Asha", "eng"), ("Ben", "eng"), ("Cara", "ops")]
rows.sort(key=lambda r: r[0])   # by name first
rows.sort(key=lambda r: r[1])   # then by team, name order preserved

Key point: The multi-level sort trick is the practical payoff of stability. Demonstrating it shows you understand why stability is a feature, not trivia.

Q28. How do you design a stack that returns its minimum in O(1)?

Keep a second stack that tracks the minimum so far. Each time you push a value, also push the current minimum (the smaller of the new value and the previous minimum) onto the min-stack. Each pop removes from both. The top of the min-stack is always the current minimum.

This gives O(1) push, pop, top, and getMin, at the cost of O(n) extra space. It's a classic because the naive approach (scanning for the min on each call) is O(n), and the question needs to see you trade space for time with a second stack.

python
class MinStack:
    def __init__(self):
        self.stack = []
        self.mins = []
    def push(self, x):
        self.stack.append(x)
        self.mins.append(min(x, self.mins[-1]) if self.mins else x)
    def pop(self):
        self.mins.pop()
        return self.stack.pop()
    def get_min(self):
        return self.mins[-1]

Key point: The insight is storing the running minimum alongside each element, not recomputing it. If you get stuck, ask yourself 'what do I need to remember at push time?'.

Q29. What is the two-pointer technique and when do you use it?

Two pointers use two indices moving through a data structure, often from opposite ends or at different speeds, to solve a problem in one pass instead of nested loops. It typically turns an O(n squared) brute force into O(n).

Use it on sorted arrays for pair-sum problems (move the pointers inward based on whether the sum is too big or small), on linked lists for cycle detection (slow and fast pointers), and for in-place operations like removing duplicates. The prerequisite is often sorted input or a structure you can traverse with two coordinated positions.

python
def two_sum_sorted(arr, target):
    lo, hi = 0, len(arr) - 1
    while lo < hi:
        s = arr[lo] + arr[hi]
        if s == target:
            return (lo, hi)
        if s < target:
            lo += 1   # need a bigger sum
        else:
            hi -= 1   # need a smaller sum
    return None

Key point: When you see 'sorted array' and 'find a pair,' think two pointers before nested loops. Naming the O(n) versus O(n squared) improvement is the point.

Q30. What is the sliding window technique?

A sliding window maintains a contiguous range (the window) over an array or string and slides it forward, expanding and shrinking to satisfy a condition, so you compute answers for all subarrays or substrings in one O(n) pass instead of re-scanning.

Fixed-size windows solve 'maximum sum of any k consecutive elements' by adding the new element and subtracting the one that left. Variable-size windows solve 'longest substring without repeating characters' by growing the right edge and shrinking the left when the condition breaks. Both avoid the O(n squared) of recomputing each window from scratch.

python
def max_sum_k(arr, k):
    window = sum(arr[:k])
    best = window
    for i in range(k, len(arr)):
        window += arr[i] - arr[i - k]  # slide: add new, drop old
        best = max(best, window)
    return best

How a variable-size sliding window scans a string

1Start the window
Put left and right at index 0. The window covers the range from left up to right.
2Grow right
Move right forward one step and add that element to the window's running count or sum.
3Check the rule
If the window still satisfies the condition, record the answer. If it breaks (a repeat character, a sum over the limit), fix it next.
4Shrink from left
Move left forward, dropping elements, until the window is valid again.
5Slide to the end
Repeat until right reaches the end. Each index enters and leaves the window once, so the whole scan stays O(n).

Fixed-size windows skip the shrink step: they add the new element and drop the one k positions back on every move.

Key point: The tell is 'contiguous subarray or substring' plus a size or condition. Recognizing it and coding the add-new-drop-old update is what the question screens for.

Q31. How do you detect a cycle in a graph?

For a directed graph, run DFS and track three states per node: unvisited, in-progress (on the current path), and done. If DFS reaches a node that's already in-progress, you've found a back edge, which means a cycle. For an undirected graph, DFS and check whether you reach a visited node that isn't the parent you came from.

The alternative for directed graphs is Kahn's algorithm (BFS-based topological sort): if you can't remove all nodes because some always have incoming edges, a cycle exists. Both are O(V + E). Cycle detection underlies dependency resolution, deadlock detection, and validating build graphs.

python
def has_cycle(graph):
    state = {}  # 0 = unvisited, 1 = in progress, 2 = done
    def dfs(node):
        state[node] = 1
        for nb in graph.get(node, []):
            if state.get(nb) == 1:      # back edge
                return True
            if state.get(nb) != 2 and dfs(nb):
                return True
        state[node] = 2
        return False
    return any(dfs(n) for n in graph if state.get(n) != 2)

Key point: The three-color (in-progress) trick is the directed-graph answer. For undirected graphs, remembering the parent check avoids a false positive on the edge you just crossed.

Q32. What is topological sort and where is it used?

A topological sort orders the nodes of a directed acyclic graph (DAG) so every edge points forward: if A must come before B, A appears first. It only exists when the graph has no cycles.

Two standard approaches: Kahn's algorithm repeatedly removes nodes with no incoming edges (in-degree zero) using a queue, and DFS-based sort pushes nodes onto a stack as their DFS finishes and reverses it. Both are O(V + E). It powers build systems, task scheduling with dependencies, course prerequisites, and package managers.

python
from collections import deque

def topo_sort(graph, indegree):
    q = deque([n for n in graph if indegree[n] == 0])
    order = []
    while q:
        node = q.popleft()
        order.append(node)
        for nb in graph[node]:
            indegree[nb] -= 1
            if indegree[nb] == 0:
                q.append(nb)
    return order if len(order) == len(graph) else None  # None = cycle

Key point: Note that a failed topological sort (fewer nodes than the graph) proves a cycle. dual purpose.

Q33. What is a trie and what problem does it solve better than a hash table?

A trie (prefix tree) stores strings by their characters: each node represents one character, and a path from the root spells a prefix. Words that share a prefix share the same path, and a flag marks where a complete word ends. Lookup and insert are O(m) where m is the word length, independent of how many words are stored.

It beats a hash table for prefix queries. Autocomplete, 'find all words starting with pre,' and spell-checking are natural on a trie because all words with a given prefix live under one subtree. A hash table can check exact membership in O(1) but can't answer 'which keys this' without scanning everything comes first.

python
class Trie:
    def __init__(self):
        self.root = {}
    def insert(self, word):
        node = self.root
        for ch in word:
            node = node.setdefault(ch, {})
        node["$"] = True  # end-of-word marker
    def starts_with(self, prefix):
        node = self.root
        for ch in prefix:
            if ch not in node:
                return False
            node = node[ch]
        return True

Key point: The winning line is 'tries answer prefix queries that hash tables can't.' If the problem says autocomplete or prefix, reach for a trie.

Watch a deeper explanation

Video: Implement Trie (Prefix Tree) - Leetcode 208 (NeetCode, YouTube)

Q34. What is memoization and how does it relate to dynamic programming?

Memoization caches the result of each subproblem the first time you compute it, so repeated calls return instantly instead of recomputing. It's the top-down form of dynamic programming: write the natural recursion, then add a cache keyed by the arguments.

Dynamic programming applies when a problem has overlapping subproblems and optimal substructure. Naive recursive Fibonacci recomputes the same values exponentially; memoizing it makes each value compute once, turning O(2^n) into O(n). The bottom-up (tabulation) form fills a table iteratively instead of recursing.

python
from functools import lru_cache

@lru_cache(maxsize=None)
def fib(n):
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)   # each value computed once

Key point: Say the two conditions for DP: overlapping subproblems and optimal substructure. Naming them shows you know when DP applies, not just how to memoize.

Q35. Walk through the ways to compute Fibonacci and their complexities.

Naive recursion is O(2^n) time because it recomputes the same subproblems exponentially, and it can blow the stack. Memoized (top-down) recursion caches each value for O(n) time and O(n) space. Bottom-up tabulation fills an array iteratively, also O(n) time, and can drop to O(1) space by keeping only the last two values.

This is a favorite because it demonstrates the whole DP progression on one problem: recognize the overlapping subproblems, add a cache, then optimize the space. The O(1)-space iterative version is the answer that closes the loop.

python
def fib(n):
    a, b = 0, 1
    for _ in range(n):
        a, b = b, a + b   # O(n) time, O(1) space
    return a

Key point: End on the O(1)-space iterative version. Showing you can optimize memory after getting the O(n) time is what pushes this from a pass to a strong signal.

Q36. How would you solve the coin change problem?

For the minimum-coins version, use bottom-up DP. Build an array dp where dp[amount] is the fewest coins to make that amount. Start dp[0] = 0, and for each amount from 1 up to the target, try every coin and take the best: dp[a] = min over coins c of dp[a - c] + 1. It's O(amount * number of coins).

The greedy 'take the largest coin first' approach fails for arbitrary coin sets (it works for standard currency by design, but not for something like coins of 1, 3, and 4 making 6). Interviewers use this specifically to see whether you reach for DP over a tempting but wrong greedy.

python
def coin_change(coins, amount):
    dp = [float('inf')] * (amount + 1)
    dp[0] = 0
    for a in range(1, amount + 1):
        for c in coins:
            if c <= a:
                dp[a] = min(dp[a], dp[a - c] + 1)
    return dp[amount] if dp[amount] != float('inf') else -1

Key point: Mention that greedy fails on non-standard coin sets. Explaining why DP is needed instead of greedy is the exact reasoning this problem tests.

Q37. When should you use recursion versus iteration?

Use recursion when the problem is naturally self-similar: tree and graph traversal, divide-and-conquer, and backtracking all read cleanly as recursion. Use iteration when the problem is a simple sequential process, or when recursion depth would risk a stack overflow.

The trade-off is clarity versus the call-stack cost. Recursion uses O(depth) stack space and, in languages without tail-call optimization (Python included), can hit a recursion limit on deep inputs. Any recursion can be rewritten as iteration with an explicit stack, which is exactly what you do when depth is a concern.

python
# recursive DFS
def dfs(node, visited):
    visited.add(node)
    for nb in graph[node]:
        if nb not in visited:
            dfs(nb, visited)

# iterative DFS with an explicit stack (no recursion limit)
def dfs_iter(start):
    stack, visited = [start], set()
    while stack:
        node = stack.pop()
        if node not in visited:
            visited.add(node)
            stack.extend(graph[node])

Key point: Knowing that any recursion converts to iteration with an explicit stack is the mature answer. It's the fix when an interviewer says 'the input can be a million deep.'

Q38. How do you detect a cycle in a linked list?

Use Floyd's tortoise-and-hare algorithm: two pointers, one moving one node at a time (slow) and one moving two (fast). If there's a cycle, the fast pointer eventually laps the slow one and they meet. If the fast pointer hits the end (None), there's no cycle.

It's O(n) time and O(1) space, better than the naive approach of storing every visited node in a set, which is O(n) space. The same two-speed idea also finds the cycle's start and the middle of a list, which is why interviewers like it.

python
def has_cycle(head):
    slow = fast = head
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
        if slow is fast:
            return True
    return False

Key point: The O(1)-space fast-slow trick is the expected answer. the naive O(n)-space set version first, then improving it, matters.

Q39. When would you choose a balanced BST over a hash table?

Choose a hash table when you only need exact key lookup, insert, and delete, and you want O(1) average. Choose a balanced BST when you need the data kept in sorted order or you need range queries, ordered iteration, or the nearest key to a value.

A hash table can't answer 'give me all keys between 10 and 50' or 'what's the smallest key larger than x' without scanning everything. A balanced BST (or a structure like a skip list) does those in O(log n) because it maintains order. The trade is O(log n) operations versus the hash table's O(1) average.

NeedHash tableBalanced BST
Exact key lookupO(1) avgO(log n)
Sorted iterationNot supportedO(n)
Range queryNot supportedO(log n + k)
Nearest / floor / ceiling keyNot supportedO(log n)

Key point: The deciding question is 'do you need order or range queries?'. If yes, BST; if no, hash table. Framing it as an ordering trade-off is the clean answer.

Q40. What does it mean for an algorithm to be in-place, and why does it matter?

An in-place algorithm transforms its input using O(1) extra space (or O(log n) for the recursion stack), modifying the data directly instead of allocating a separate copy. Reversing an array by swapping ends inward is in-place; building a new reversed array is not.

It matters when memory is tight or the data is huge. In-place quicksort sorts without a second array; the two-pointer 'remove duplicates from a sorted array' runs in O(1) space. Interviewers often add 'do it in-place' as a constraint precisely to see whether you can avoid the easy extra-space solution.

Key point: When a problem says 'in-place' or 'O(1) extra space,' it's steering you toward swaps and pointers rather than a new data structure. Treat it as a strong hint.

Q41. How do you find the shortest path in a weighted graph?

For non-negative weights, use Dijkstra's algorithm: keep a min-heap (priority queue) of nodes by their tentative distance, repeatedly pop the closest unfinished node, and relax its neighbors. With a heap it's O((V + E) log V). For unweighted graphs, plain BFS already gives the shortest path in O(V + E).

If the graph has negative edge weights, Dijkstra breaks; use Bellman-Ford (O(V * E)), which also detects negative cycles. For all-pairs shortest paths on a small dense graph, Floyd-Warshall is O(V cubed). Naming the right algorithm for the weight condition is the whole point of the question.

python
import heapq

def dijkstra(graph, start):
    dist = {start: 0}
    pq = [(0, start)]
    while pq:
        d, node = heapq.heappop(pq)
        if d > dist.get(node, float('inf')):
            continue
        for nb, w in graph[node]:
            nd = d + w
            if nd < dist.get(nb, float('inf')):
                dist[nb] = nd
                heapq.heappush(pq, (nd, nb))
    return dist

Key point: Match the algorithm to the weights: unweighted is BFS, non-negative is Dijkstra, negative is Bellman-Ford. Stating that mapping in practice is exactly what the question checks.

Q42. What is amortized analysis? Give an example.

Amortized analysis measures the average cost of an operation over a sequence, so occasional expensive operations are spread across many cheap ones. It's not the same as average-case (which is about random inputs); amortized is a guarantee over any sequence.

The classic example is appending to a dynamic array. Most appends are O(1), but when the array fills, it doubles its capacity and copies everything, an O(n) operation. Because doublings are rare and each copies roughly what all the cheap appends since the last doubling added, the amortized cost per append is O(1).

Key point: Use the dynamic-array doubling example and the word 'amortized O(1).' It's the cleanest demonstration and the one the question expects to hear.

Q43. How would you design an LRU cache with O(1) operations?

Combine a hash table with a doubly linked list. The hash table maps keys to nodes for O(1) lookup. The doubly linked list keeps nodes in usage order: most-recently-used at the front, least-recently-used at the back. On access, move the node to the front; on insert past capacity, evict the tail.

Both get and put are O(1) because the hash table finds the node instantly and the doubly linked list lets you unlink and re-insert in constant time. In Python, collections.OrderedDict (or a plain dict since 3.7, using move_to_end) gives this for free, which is worth mentioning after you've shown you know the underlying design.

python
from collections import OrderedDict

class LRUCache:
    def __init__(self, capacity):
        self.cache = OrderedDict()
        self.cap = capacity
    def get(self, key):
        if key not in self.cache:
            return -1
        self.cache.move_to_end(key)   # mark recently used
        return self.cache[key]
    def put(self, key, value):
        self.cache[key] = value
        self.cache.move_to_end(key)
        if len(self.cache) > self.cap:
            self.cache.popitem(last=False)  # evict LRU

Key point: Describe the hash-table-plus-doubly-linked-list design first, then mention OrderedDict as the shortcut. Leading with the design proves you understand why it's O(1).

Q44. How do you validate that a binary tree is a valid BST?

The reliable way is to pass down a valid range for each node. The root can be anything; a left child must be less than its parent and within the parent's lower bound; a right child must be greater and within the upper bound. Recurse with tightening bounds and check each node falls inside its range.

The common wrong answer only compares each node to its immediate children, which misses violations deeper in the tree. An alternative correct method is an inorder traversal: a valid BST produces strictly increasing values, so you check that each visited value is greater than the previous one.

python
def is_valid_bst(node, low=float('-inf'), high=float('inf')):
    if node is None:
        return True
    if not (low < node.val < high):
        return False
    return (is_valid_bst(node.left, low, node.val) and
            is_valid_bst(node.right, node.val, high))

Key point: Call out the trap: checking only parent-child pairs is wrong. The min/max bounds approach or the inorder-is-increasing property is what makes it correct.

Q45. What is backtracking and how does it differ from brute force?

Backtracking builds a solution incrementally and abandons a partial candidate the moment it can't lead to a valid answer, then undoes the last choice and tries another. It's a smarter brute force that prunes dead branches instead of enumerating every full combination.

It fits constraint problems: generating permutations and subsets, solving Sudoku or N-queens, and word searches. The The technical sequence is a recursive 'choose, explore, un-choose' loop. Pruning early is what makes it feasible where naive brute force would be hopelessly slow.

python
def permutations(nums):
    result = []
    def backtrack(path, remaining):
        if not remaining:
            result.append(path[:])
            return
        for i in range(len(remaining)):
            path.append(remaining[i])                 # choose
            backtrack(path, remaining[:i] + remaining[i+1:])  # explore
            path.pop()                                # un-choose
    backtrack([], nums)
    return result

Key point: The 'choose, explore, un-choose' skeleton is the pattern to internalize. If you can write that loop from memory, most backtracking problems become fill-in-the-blank.

Q46. What are common bit manipulation tricks worth knowing?

The building blocks: x & 1 tests the lowest bit (odd/even), x << k multiplies by 2^k, x >> k divides, x & (x - 1) clears the lowest set bit, and XOR (^) toggles bits. XOR has a useful property: a ^ a is 0 and a ^ 0 is a.

That XOR property solves the 'find the single number where every other appears twice' problem in O(n) time and O(1) space: XOR all numbers and the pairs cancel, leaving the unique one. Bit manipulation shows up in problems about sets of flags, counting set bits, and space-tight uniqueness checks.

python
def single_number(nums):
    result = 0
    for n in nums:
        result ^= n   # pairs cancel, unique survives
    return result

Key point: The XOR-cancels-pairs trick is the one interviewers love. Explaining why (a ^ a == 0) matters more than reciting the operator.

Q47. What is a prefix sum array and what problem does it optimize?

A prefix sum array stores, at each index, the running total of all elements up to that point. Once built in O(n), it answers 'what's the sum of the range from i to j?' in O(1) by subtracting prefix[i] from prefix[j+1], instead of re-adding the range each time.

It turns repeated range-sum queries from O(n) each into O(1) each, a big win when you have many queries on a static array. The same idea extends to 2D grids for rectangle sums, and pairs with a hash table to solve 'count subarrays that sum to k' in one pass.

python
def build_prefix(arr):
    prefix = [0]
    for x in arr:
        prefix.append(prefix[-1] + x)
    return prefix

p = build_prefix([2, 4, 6, 8])
p[3] - p[1]   # sum of indices 1..2 = 4 + 6 = 10

Key point: The trade to state: prefix sums give O(1) range queries but O(n) to update, so they fit static arrays. If updates are frequent, A Fenwick or segment tree instead matters.

Back to question list

Data Structure Interview Questions for Experienced Developers

Experienced18 questions

advanced rounds probe design judgment, scale, and the trade-offs behind the structures. Expect every answer to draw a follow-up.

Q48. How do you choose a data structure for a system that must scale?

Start from the access pattern, not the data. Profile which operation dominates (read-heavy, write-heavy, range scans, point lookups) and its required latency, then pick the structure whose Big-O matches, and only then consider memory and cache behavior. A structure that's theoretically optimal but cache-hostile can lose to a simpler contiguous one.

At scale the answer often stops being a single in-memory structure and becomes a system: a hash index for point lookups, a B-tree or LSM-tree for on-disk sorted access (why databases use them), a bloom filter to skip disk reads for absent keys. The production signal is reasoning from workload to structure, and knowing when the structure lives on disk, not just in RAM.

Key point: Lead with 'what's the dominant operation and its latency budget?'. Reasoning from the workload rather than naming a favorite The technical sequence is what separates production-ready answers.

Q49. Why do databases use B-trees instead of binary search trees?

A B-tree is a balanced tree where each node holds many keys and has many children, keeping the tree very shallow. Databases store data on disk, and disk reads happen in blocks (pages). A B-tree node is sized to one page, so a single disk read loads many keys at once, minimizing the number of slow disk seeks.

A binary search tree has only two children per node, so its height is much larger and every step down the tree could be a separate disk read. For n keys, a B-tree of order m has height around log_m(n) versus log_2(n) for a BST, a big difference in disk I/O. The whole design optimizes for the fact that disk access dominates cost.

Key point: The core insight is 'minimize disk seeks by packing many keys per node.' Tying tree shape to the block-based nature of disk I/O is the answer that indicates database experience.

Q50. What is a bloom filter and what trade-off does it make?

A bloom filter is a space-efficient probabilistic structure that answers 'is this element possibly in the set?' with two possible replies: definitely not, or probably yes. It uses a bit array and several hash functions; adding an element sets several bits, and a query checks whether all those bits are set.

The trade-off: it can produce false positives (says 'probably yes' when the element is absent) but never false negatives. That's acceptable when a false positive just triggers a cheap fallback check. Uses include skipping disk lookups for keys that definitely aren't stored, deduplicating a massive stream, and CDN cache checks. You spend a little accuracy to save enormous memory.

Key point: The one-liner is 'no false negatives, occasional false positives, tiny memory.' Naming a real use (skip a disk read for an absent key) grounds it.

Q51. What is consistent hashing and what problem does it solve?

Consistent hashing maps both keys and servers onto a ring (a hash space). A key belongs to the first server clockwise from its position. When a server is added or removed, only the keys in its arc move; the rest stay put.

It solves the rehashing storm of naive hash-mod-N sharding. With hash(key) % N, changing the number of servers remaps almost every key, which is catastrophic for a distributed cache or database. Consistent hashing limits movement to roughly 1/N of keys per change, and virtual nodes smooth out the load distribution. It's foundational to systems like distributed caches and sharded stores.

Key point: Contrast it directly with hash-mod-N to show the payoff: naive sharding remaps everything on a resize, consistent hashing moves only a fraction. That contrast is the point.

Q52. What is the union-find (disjoint set) structure and where is it used?

Union-find tracks a collection of disjoint sets and supports two operations: find (which set is this element in?) and union (merge two sets). It's built as a forest where each element points toward its set's representative root.

Two optimizations make it near-constant time: path compression flattens the tree during find, and union by rank attaches the smaller tree under the larger. Together they give nearly O(1) amortized (inverse Ackermann) per operation. It powers Kruskal's minimum spanning tree, detecting whether adding an edge creates a cycle, and grouping connected components, like counting islands or friend circles.

python
class UnionFind:
    def __init__(self, n):
        self.parent = list(range(n))
    def find(self, x):
        while self.parent[x] != x:
            self.parent[x] = self.parent[self.parent[x]]  # path compression
            x = self.parent[x]
        return x
    def union(self, a, b):
        self.parent[self.find(a)] = self.find(b)

Key point: If a problem involves 'connected components,' 'grouping,' or 'does this edge close a cycle,' union-find is often the intended tool. Naming path compression shows you know why it's fast.

Q53. What is an LSM-tree and why do write-heavy databases use it?

A log-structured merge-tree buffers writes in memory (a memtable, often a balanced tree or skip list), then flushes them as sorted immutable files (SSTables) to disk. Background compaction merges those files to keep read cost bounded and reclaim space from overwritten or deleted keys.

It's chosen for write-heavy workloads because writes are sequential appends, which disks and SSDs handle far faster than the random writes a B-tree makes when updating pages in place. The cost is slower reads (a key might live in several SSTables) and write amplification from compaction, which bloom filters and tiered compaction mitigate. It backs many modern stores; the B-tree-versus-LSM choice is a read-optimized-versus-write-optimized trade.

Key point: Frame it as the write-optimized counterpart to the read-optimized B-tree. Naming sequential writes as the win and read amplification as the cost is the balanced production-ready answer.

Q54. What is a skip list and why might you use one over a balanced tree?

A skip list is a layered linked list: the bottom layer holds all elements in order, and higher layers hold progressively sparser 'express lanes' that let you skip ahead. Searching drops down levels, giving O(log n) expected search, insert, and delete, the same as a balanced tree.

The appeal is simplicity and concurrency. Skip lists are far easier to implement correctly than a red-black tree, and they lend themselves to lock-free concurrent implementations because operations touch localized pointers. That's why Redis uses them for sorted sets and Java's ConcurrentSkipListMap exists. The trade is probabilistic balance (based on random levels) rather than a strict guarantee.

Key point: The two selling points are simpler implementation and easier concurrency than balanced trees. Citing Redis sorted sets as a real user makes the answer concrete.

Q55. How does CPU cache behavior affect data structure choice?

Modern CPUs are far faster than main memory, so they cache recently and nearby data in lines of about 64 bytes. Structures with contiguous memory (arrays, vectors) get many useful elements per cache line and iterate fast. Pointer-chasing structures (linked lists, node-based trees) scatter data, causing cache misses that can dominate runtime.

This is why an O(n) linear scan over an array often beats an O(log n) search over a scattered tree for small-to-medium n, and why a hash table with open addressing can outperform one with chaining. The senior point: Big-O ignores constants, but cache misses are a huge constant, so on real hardware the memory layout can matter as much as the asymptotic complexity.

Key point: The nuance the technical value is is 'Big-O hides constants, and cache misses are a giant constant.' Giving a case where the worse Big-O wins in practice shows depth.

Q56. What makes designing concurrent data structures hard?

The core difficulty is that operations which look atomic (read-modify-write) aren't, so two threads can interleave and corrupt shared state or see torn reads. Coarse locking (one lock for the whole structure) is correct but kills throughput; fine-grained locking (per-node or per-bucket) scales better but invites deadlock and is hard to reason about.

Lock-free structures use atomic compare-and-swap to make progress without locks, avoiding deadlock but battling the ABA problem and subtle memory-ordering rules. The pragmatic answer for most systems: reach for battle-tested concurrent structures from the standard library (concurrent maps, queues) before hand-rolling one, because correct lock-free code is genuinely expert territory.

Key point: The mature closing line is 'use proven library structures before writing lock-free code.' It signals you respect how easy concurrency is to get subtly wrong.

Q57. What is a count-min sketch and when would you reach for it?

A count-min sketch is a probabilistic structure that estimates the frequency of elements in a stream using sublinear memory. It's a 2D array of counters with several hash functions; each element increments one counter per row, and a query returns the minimum across those rows to reduce overcounting.

You reach for it when exact counts are too memory-expensive: finding heavy hitters in a huge traffic stream, tracking approximate frequencies for rate limiting, or trending-item detection. Like a bloom filter, it trades accuracy for space, it can overestimate but never underestimate, which is acceptable when an approximate top-k is enough.

Key point: Group it mentally with bloom filters as a probabilistic streaming structure: trade exactness for tiny memory. 'heavy hitters' or 'approximate top-k' matters.

Q58. What are persistent (immutable) data structures and how do they stay efficient?

A persistent data structure preserves previous versions when modified: updating it returns a new version while the old one stays valid and unchanged. Naively that means copying everything, which is too slow. The trick is structural sharing: the new version reuses most of the old one and only creates the nodes along the path that changed.

A persistent tree update copies just the O(log n) nodes from the changed leaf to the root and shares the rest. This backs functional languages' immutable collections, version control's history, undo systems, and Clojure's data structures. The payoff is cheap versioning and thread-safety by default, because nothing is ever mutated in place.

Key point: Structural sharing is the key phrase: only the changed path is copied, the rest is reused. That's what makes immutability O(log n) instead of O(n) per update.

Q59. How do you sort data that doesn't fit in memory?

Use external merge sort. Read the data in chunks that fit in memory, sort each chunk, and write it back to disk as a sorted run. Then merge the sorted runs together with a k-way merge using a min-heap, reading a little from each run at a time and writing the merged output sequentially.

It's O(n log n) comparisons like in-memory sorting, but the design minimizes disk I/O by doing sequential reads and writes rather than random access. The number of merge passes depends on how many runs fit in memory at once. This is how databases and big-data tools sort terabytes on machines with gigabytes of RAM.

python
import heapq

def k_way_merge(sorted_runs):
    # sorted_runs: list of already-sorted iterables (disk chunks)
    return list(heapq.merge(*sorted_runs))  # min-heap merge, streaming

Key point: The The technical sequence is 'sort chunks that fit, then k-way merge with a heap.' Emphasizing sequential I/O over random access is what marks the answer as production-aware.

Q60. How would you process a graph too large to fit in memory?

First, store it compactly: an adjacency list (or compressed formats like CSR, compressed sparse row) rather than a matrix, since real graphs are sparse. If it still won't fit, partition it across machines and process with a vertex-centric model (like Pregel or GraphX) where each vertex computes based on messages from its neighbors, iterating in supersteps.

The design pressure is minimizing cross-partition edges, because messages crossing machines are the bottleneck, so graph partitioning quality drives performance. For streaming-scale problems you may accept approximate answers (sampling, sketches) instead of exact traversals. The production signal is recognizing that at scale the algorithm choice bends around the memory and network layout, not the other way around.

Key point: The escalation ladder is: compact representation, then partition, then approximate. Naming CSR and vertex-centric processing shows you've thought past in-memory graphs.

Q61. What is a rolling hash and where is it used?

A rolling hash computes the hash of a fixed-size window over a sequence, and when the window slides by one, updates the hash in O(1) by removing the outgoing element's contribution and adding the incoming one, instead of rehashing the whole window.

It powers the Rabin-Karp string search: to find a pattern in text, hash the pattern once and roll a same-size hash across the text, comparing full strings only when hashes match. Average O(n + m). Rolling hashes also drive content-defined chunking in deduplication and rsync-style diffing. The trade is handling hash collisions with a verification step.

Key point: The O(1) window update is the whole trick. Mentioning Rabin-Karp and that you verify on a hash match (to guard against collisions) rounds out a strong answer.

Q62. What is a monotonic stack and what class of problems does it solve?

A monotonic stack keeps its elements in sorted order (increasing or decreasing) by popping any element that would violate the order before pushing a new one. Because each element is pushed and popped at most once, a whole class of problems drops from O(n squared) to O(n).

It solves 'next greater element,' 'largest rectangle in a histogram,' 'daily temperatures,' and 'trapping rain water.' The pattern: as you scan, the stack holds candidates still waiting for their answer, and you resolve them the moment a qualifying element arrives. Recognizing that a problem asks for the nearest larger or smaller element in a sequence is the cue to reach for it.

python
def next_greater(nums):
    result = [-1] * len(nums)
    stack = []  # holds indices, values decreasing
    for i, n in enumerate(nums):
        while stack and nums[stack[-1]] < n:
            result[stack.pop()] = n
        stack.append(i)
    return result

Key point: The recognition cue is 'nearest greater or smaller element.' If you spot that, a monotonic stack turns an O(n squared) scan into O(n), which is the whole reason the pattern exists.

Q63. What is a segment tree and when is it worth the complexity?

A segment tree is a binary tree over an array where each node stores an aggregate (sum, min, max) of a range, so you can query the aggregate of any range and update any element, both in O(log n). Building it is O(n).

It's worth it when you have many interleaved range queries and point updates on the same array. A prefix-sum array answers range sums in O(1) but can't handle updates without an O(n) rebuild; a segment tree keeps both operations O(log n). Fenwick trees (binary indexed trees) do the same for simpler aggregates with less code and memory, so mention them as the lighter alternative when the aggregate is a sum.

Key point: The trigger is 'range queries plus updates.' Contrasting it with prefix sums (fast query but O(n) update) shows you know exactly when the extra structure earns its cost.

Q64. A senior interviewer says 'design a structure for autocomplete on 10 million entries.' How do you approach it?

Clarify first: how many queries per second, do results need ranking (by popularity), is the dataset static or updated live, and what latency budget? Those answers change the design. Then propose: a trie or compressed trie (radix tree) for prefix matching, with each node caching its top-k completions so a lookup returns ranked results without traversing the whole subtree.

The closing step is scale and operations: the trie can be sharded by prefix across machines, popularity scores feed the ranking, and for fuzzy matching you'd add edit-distance handling or an n-gram index. The technical sequence, clarify then core structure then ranking then scale, is what a advanced round is really scoring, not any single data structure.

Key point: This is a system-design question in a data-structures costume. Clarifying requirements before naming a structure, and caching top-k at each trie node, is the pair of moves that lands it.

Q65. When does amortized O(1) become a problem, and how do you fix it?

Amortized O(1) means the average cost over many operations is constant, but individual operations can spike. A dynamic array's occasional doubling copies the whole array in one O(n) step, and a hash table's resize rehashes everything at once. That's fine for throughput but bad for latency-sensitive systems, where one request unluckily eats the full resize and blows the tail latency (p99).

The fix is to spread the expensive work out. Incremental resizing rehashes a few entries per operation instead of all at once, keeping every operation bounded, which is what low-latency stores and real-time systems do. The production insight is that amortized analysis optimizes total work, but tail latency cares about the worst single operation, so the two goals can conflict.

Key point: The distinction to draw is amortized cost versus worst-case per-operation latency. Naming p99 tail latency and incremental resizing as the fix is what signals real production experience.

Back to question list

Choosing a Data Structure: Arrays vs Linked Lists vs Hash Tables vs Trees

Picking a The technical sequence is a trade-off between how fast each operation runs and how much memory it costs. Arrays give O(1) access by index but O(n) insertion in the middle. Linked lists flip that: O(1) insertion at a known node but O(n) to find anything. Hash tables give O(1) average search and insert but no ordering and worst-case O(n) under bad collisions. Balanced trees keep data sorted with O(log n) across the board. Interviewers ask you to reason through this table out loud, because naming the operation your problem does most often is how you land on the right structure. The complexities below are average or expected cost; note where the worst case differs.

StructureAccessInsertSearchBest at
Array (dynamic)O(1)O(n)O(n)Index access, cache-friendly iteration
Linked listO(n)O(1) at nodeO(n)Frequent insert/delete at known positions
Hash tableN/AO(1) avgO(1) avgKey lookup, deduplication, counting
Balanced BSTO(log n)O(log n)O(log n)Sorted data with fast search and range queries
Heap (binary)O(1) peekO(log n)O(n)Repeatedly getting the min or max

How to Prepare for a Data Structures Interview

Prepare in layers, and practice out loud. Most DSA rounds move from a concept check to a live coding problem where the interviewer watches you think. Rehearse the coding-round approach as its own skill: the candidates who fail rarely fail on knowledge, they fail by diving into code before understanding the problem.

  • Master your tier's structures until you can state each operation's Big-O without notes, then read one tier up for the stretch questions.
  • Type and run every snippet; implementing a structure once teaches more than reading about it ten times.
  • Practice the four-step coding-round flow below on timed problems, because your process, not just whether the code compiles is the technical point.
  • Take the quiz on this page to find weak spots, then revisit those questions before the real round.

The coding-round approach the question needs to see

1Clarify
restate the problem, ask about input size, edge cases, and constraints before writing anything
2Examples
walk one small input by hand, including an empty or single-element case
3Approach
name the data structure and algorithm, state the Big-O, and confirm before coding
4Code and test
write it, then trace your example and the edge cases out loud to catch bugs

Earlier rounds increasingly run as AI coding interviews. This clarify-examples-approach-code-test loop is exactly what automated evaluation indicates strong problem-solving.

Test Yourself: Data Structures Quiz

Ready to test your Data Structures knowledge?

10 questions, about 6 minutes. Score 70% or higher to earn a shareable certificate.

10 questions Instant feedback Free certificate on 70%+

Frequently  Asked  Questions

Which Data Structures topics should I prioritize before a technical round?

Prioritize the areas that change real decisions: fundamentals, practical tasks, debugging, trade-offs, and evidence. The highest-value questions are tied to a project example, a validation check, or a failure mode.

Do I have to answer in a specific programming language?

Almost never. Most coding rounds let you pick, so use the language you're fastest and most confident in. This page uses Python because it reads cleanly, but the structures, algorithms, and Big-O costs are identical in Java, C++, JavaScript, or Go. State your language at the start so the interviewer knows what to expect.

Are these questions enough to pass a data structures interview?

They cover the question-answer portion well, but most DSA rounds also include live coding: solving a problem under observation while explaining your thinking. implementing structures and solving small problems out loud with a timer is the practice path. Our AI coding interview prep guide covers that format, including how automated evaluation reads your process.

How much math or Big-O do I really need?

You need to state and compare the common complexities (O(1), O(log n), O(n), O(n log n), O(n squared)) and explain why an algorithm has the cost it does. You don't need formal proofs. If you can look at a nested loop and say O(n squared), and explain why a balanced tree gives O(log n), you're at the bar for most rounds.

How long does it take to prepare for a DSA interview?

If you already code daily, one to two weeks of focused practice covers this bank with implementation runs. Starting colder, plan four to six weeks and write code every day; reading answers without implementing the structures is how preparation quietly fails. Implement each structure at least once from scratch.

Is there a way to test my data structures knowledge?

Yes. The quiz on this page scores you as you go and explains every answer, so you find your weak spots fast. Pass the threshold and you can download a shareable certificate with your name and score, free and with no sign-up. It's the quickest way to check yourself before the real round.

From the team that builds coding interviews

Hyring builds the AI Coding Interviewer that runs live technical rounds in 20+ languages, where data structures and algorithms are the core of what gets evaluated. These questions reflect what actually gets asked inside the DSA interviews we host for 5,000+ HR teams.

See how the AI Coding Interviewer works

Sources

Adithyan RKWritten by Adithyan RK
Surya N
Fact-checked by Surya N
Published on: 6 Jun 2026Last updated: 10 Jul 2026
Share: