Dev Encyclopedia
ArticlesToolsContactAbout

Get notified when new content drops

No spam. Just new articles, tools, and updates straight to your inbox.

Dev Encyclopedia

A reference for builders

Content

  • Articles
  • Tools
  • About
  • Contact

Connect

  • support@devencyclopedia.com
  • RSS Feed

Legal

  • Privacy Policy
  • Terms of Service
  • Disclaimer

© 2026 Dev Encyclopedia

Back to top ↑
  1. Home
  2. /Blog
  3. /50 DSA Interview Questions and Answers (2026)
programming46 min read

50 DSA Interview Questions and Answers (2026)

50 DSA interview questions covering arrays, linked lists, trees, graphs, dynamic programming, and the 8 coding patterns that cover most interview problems. Updated for 2026

Zeeshan Tofiq
Zeeshan Tofiq
June 27, 2026
On this page

On this page

  • Category 1: Fundamentals and Complexity (Q1-Q8)
  • Q1. What is a data structure and why does choosing the right one matter?
  • Q2. What is the difference between linear and non-linear data structures?
  • Q3. What is Big-O notation and why do interviewers care about it?
  • Q4. What is the difference between time complexity and space complexity?
  • Q5. What is an abstract data type (ADT) and how does it differ from a data structure?
  • Q6. What are the trade-offs between arrays and linked lists?
  • Q7. What is amortized time complexity?
  • Q8. When would you choose a hash map over an array, and what's the catch?
  • Category 2: Arrays and Strings (Q9-Q16)
  • Q9. Two Sum
  • Q10. Maximum Subarray (Kadane's Algorithm)
  • Q11. Product of Array Except Self
  • Q12. Merge Intervals
  • Q13. Longest Substring Without Repeating Characters
  • Q14. Valid Anagram
  • Q15. Move Zeroes
  • Q16. Rotate Array
  • Category 3: Linked Lists, Stacks, and Queues (Q17-Q23)
  • Q17. Reverse a Linked List
  • Q18. Detect a Cycle in a Linked List (Floyd's)
  • Q19. Merge Two Sorted Linked Lists
  • Q20. Valid Parentheses
  • Q21. Design an LRU Cache
  • Q22. Implement a Queue using two Stacks
  • Q23. Find the Middle of a Linked List
  • Category 4: Trees and Graphs (Q24-Q32)
  • Q24. What are the three types of binary tree traversal?
  • Q25. What is the difference between BFS and DFS?
  • Q26. Validate a Binary Search Tree
  • Q27. Find the Lowest Common Ancestor
  • Q28. Number of Islands
  • Q29. Course Schedule (cycle detection)
  • Q30. Maximum Depth of a Binary Tree
  • Q31. Determine if two Binary Trees are the same
  • Q32. Adjacency list vs adjacency matrix
  • Category 5: Coding Patterns Reference (Q33-Q40)
  • Q33. Two Pointers pattern
  • Q34. Sliding Window pattern
  • Q35. Fast and Slow Pointers pattern
  • Q36. Backtracking pattern
  • Q37. Binary Search variants
  • Q38. Topological Sort
  • Q39. Monotonic Stack
  • Q40. Union-Find (Disjoint Set Union)
  • Category 6: Dynamic Programming and Greedy (Q41-Q47)
  • Q41. What is Dynamic Programming and when should you reach for it?
  • Q42. Climbing Stairs
  • Q43. Coin Change
  • Q44. Longest Common Subsequence
  • Q45. 0/1 Knapsack
  • Q46. House Robber
  • Q47. When does a Greedy approach fail where DP succeeds?
  • Category 7: Sorting, Searching, and Wrap-Up (Q48-Q50)
  • Q48. Compare common sorting algorithms
  • Q49. Binary Search variants beyond exact match
  • Q50. Common DSA interview mistakes
  • Quick Reference: All 50 Questions at a Glance
  • Frequently Asked Questions

DSA is the single biggest filter in technical interviews, regardless of company size or which language the role uses. The same core data structures and algorithmic patterns show up everywhere: at startups, mid-size product companies, and FAANG alike.

The candidates who do well aren't the ones who've memorized 500 individual LeetCode problems. They're the ones who recognize patterns: "this is a sliding window problem," "this needs two pointers," "this is a graph problem in disguise." Roughly 80 to 90 percent of interview problems map onto a handful of recurring patterns once you learn to see them.

These 50 questions cover both layers: the conceptual fundamentals every candidate should know cold, and the pattern-driven coding problems that make up the bulk of real interviews, each with working code and stated time/space complexity. If you're also preparing for object-oriented design rounds, our OOP interview questions guide covers the four pillars, SOLID principles, and design patterns.

💡 How to use this guide

Skim the Quick Reference table near the bottom to see all 50 questions and their core concept at a glance. Then use the table of contents to jump into the category you need most. Categories 1 through 3 cover fundamentals and the most common data structure questions. Category 5 (coding patterns) is the highest-leverage section: learning 8 patterns covers the vast majority of interview problems. Category 6 (dynamic programming) is where mid-level and senior candidates get separated.

Category 1: Fundamentals and Complexity (Q1-Q8)

Fundamental questions test whether you understand trade-offs before writing code. Interviewers use these to assess whether you think about WHY you reach for a particular data structure, not just whether you can use one.

Q1. What is a data structure and why does choosing the right one matter?

A data structure is a way of organizing and storing data so it can be accessed and modified efficiently. The choice of data structure directly determines how fast your operations run and how much memory your program uses.

The classic interview framing: the same logical problem (storing a collection of items) can be solved with an array, a linked list, a hash map, or a tree, but each choice has dramatically different performance characteristics for insertion, deletion, and lookup. Picking the wrong structure for the access pattern your problem actually needs is one of the most common reasons candidates get a working solution that's too slow.

Why interviewers ask this first: it tests whether you think about trade-offs before writing code, rather than defaulting to the same structure (usually an array or a hash map) for every problem regardless of fit.

Q2. What is the difference between linear and non-linear data structures?

Linear data structures arrange elements sequentially, where each element connects to its previous and next neighbor in a single line. Arrays, linked lists, stacks, and queues are all linear. You can traverse the entire structure in one pass, start to end.

Non-linear data structures organize elements hierarchically or as a network, where an element can connect to multiple other elements in non-sequential ways. Trees and graphs are non-linear. There is no single "start to end" traversal order; you choose a traversal strategy (BFS, DFS, in-order, etc.) based on what you need.

text
Linear:     [1] -> [2] -> [3] -> [4]

Non-linear:        [1]
                   / \
                 [2]   [3]
                 /
               [4]

This distinction matters because the algorithms you reach for differ completely. Linear structures favor simple iteration. Non-linear structures require explicit traversal algorithms (recursion, BFS queues, DFS stacks) since there's no single natural order to visit every element.

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

Big-O notation describes how an algorithm's running time or space requirement grows as the input size grows, in the worst case. It abstracts away machine-specific constants and focuses on the growth rate, which is what actually matters as input scales from 10 elements to 10 million.

python
def find_max(arr):          # O(n), must check every element once
    max_val = arr[0]
    for num in arr:
        if num > max_val:
            max_val = num
    return max_val

def binary_search(arr, target):  # O(log n), halves the search space each step
    left, right = 0, len(arr) - 1
    while left <= right:
        mid = (left + right) // 2
        if arr[mid] == target:
            return mid
        elif arr[mid] < target:
            left = mid + 1
        else:
            right = mid - 1
    return -1

Common complexity classes, from best to worst: O(1) constant, O(log n) logarithmic, O(n) linear, O(n log n) linearithmic, O(n²) quadratic, O(2^n) exponential, O(n!) factorial.

Interviewers care because a "correct" solution that's O(n²) when an O(n log n) or O(n) solution exists is usually considered an incomplete answer at any company that takes the technical round seriously. They are testing whether you can reason about scalability, not just produce output that matches expected test cases.

Q4. What is the difference between time complexity and space complexity?

Time complexity measures how the running time of an algorithm grows relative to input size. Space complexity measures how the memory usage grows relative to input size, including any extra data structures the algorithm allocates (not counting the input itself, which is usually excluded from the count).

python
def sum_array(arr):           # Time: O(n), Space: O(1)
    total = 0
    for num in arr:           # one pass through n elements
        total += num
    return total              # no extra space that scales with n

def reverse_array(arr):       # Time: O(n), Space: O(n)
    return [arr[i] for i in range(len(arr) - 1, -1, -1)]
    # creates a NEW list of size n, this is the extra space

A very common interview follow-up: "can you do this in O(1) space?" For the reverse example, yes, by reversing in place with two pointers swapping elements:

python
def reverse_in_place(arr):    # Time: O(n), Space: O(1)
    left, right = 0, len(arr) - 1
    while left < right:
        arr[left], arr[right] = arr[right], arr[left]
        left += 1
        right -= 1

This time/space trade-off question comes up constantly: many problems can trade extra memory (a hash map, a visited set) for faster time, or vice versa, and naming that trade-off explicitly is a strong signal in interviews.

Q5. What is an abstract data type (ADT) and how does it differ from a data structure?

An abstract data type defines the behavior and operations a structure supports, without specifying how those operations are implemented. A data structure is the concrete implementation that fulfills that contract.

The classic example: a Stack is an ADT. It defines push, pop, and peek operations with LIFO (last-in-first-out) behavior. You can implement that Stack ADT using an array or a linked list, two completely different concrete data structures fulfilling the same abstract contract.

python
# The Stack ADT implemented with a Python list (dynamic array)
class ArrayStack:
    def __init__(self):
        self.items = []
    def push(self, item):
        self.items.append(item)
    def pop(self):
        return self.items.pop()
    def peek(self):
        return self.items[-1]

# The same Stack ADT implemented with a linked list
class Node:
    def __init__(self, value, next=None):
        self.value = value
        self.next = next

class LinkedListStack:
    def __init__(self):
        self.top = None
    def push(self, item):
        self.top = Node(item, self.top)
    def pop(self):
        value = self.top.value
        self.top = self.top.next
        return value
    def peek(self):
        return self.top.value

Both classes satisfy the same Stack ADT contract with different underlying implementations and different trade-offs (array-backed stacks have better cache locality; linked-list-backed stacks avoid resizing costs).

Q6. What are the trade-offs between arrays and linked lists?

ArrayLinked List
Access by indexO(1)O(n), must traverse from head
Insert/delete at startO(n), shift all elementsO(1)
Insert/delete at endO(1) amortized (dynamic array)O(1) if tail pointer kept
Insert/delete in middleO(n), shift elementsO(n) to find, O(1) to insert
Memory layoutContiguousScattered, extra pointer overhead
Cache performanceGood (contiguous memory)Poor (pointer chasing)

Arrays win when you need fast random access by index and the size is roughly known upfront. Linked lists win when you need frequent insertions/deletions at the beginning or in the middle, and don't need fast random access.

A common follow-up: "why are arrays generally faster in practice for most real-world code, even when Big-O analysis suggests linked lists should be competitive?" The answer is cache locality. Array elements sit in contiguous memory, so the CPU can prefetch them efficiently. Linked list nodes are scattered across memory (pointer chasing), causing cache misses that Big-O notation doesn't capture at all.

Q7. What is amortized time complexity?

Amortized complexity describes the average performance of an operation over a sequence of operations, even when some individual operations are much more expensive than others. It's the answer to "but doesn't resizing make array append slow sometimes?"

The classic example is dynamic array append (Python's list.append(), Java's ArrayList.add()). Most appends are O(1): there's room, you just place the new element. But occasionally, the array is full and must be resized, which means allocating a new larger array and copying every existing element, an O(n) operation.

python
# Dynamic array doubling strategy
# Capacity: 1 -> 2 -> 4 -> 8 -> 16 ...
# Most appends: O(1). Occasional resize: O(n).
# Averaged ("amortized") over many appends: O(1) per append.

Because the array typically doubles in size when it resizes, expensive resize operations become exponentially rarer as the array grows. When you average the total cost of n appends across all n operations, it works out to O(1) per operation on average, even though some individual operations are O(n). This is what "amortized O(1)" means, and it's worth being able to explain the doubling-strategy reasoning, not just cite the term.

Q8. When would you choose a hash map over an array, and what's the catch?

Hash maps provide average O(1) lookup, insertion, and deletion by key, compared to O(n) for searching an unsorted array or O(log n) for a sorted array with binary search. Use a hash map whenever you need fast lookups by some key value rather than by position/index.

python
# O(n) lookup in array
def contains_array(arr, target):
    return target in arr  # must scan, worst case O(n)

# O(1) average lookup in hash set
def contains_hashset(items_set, target):
    return target in items_set  # average O(1) via hashing

The catch interviewers want you to name: hash map performance assumes a good hash function with minimal collisions. In the worst case (many keys hashing to the same bucket), operations degrade to O(n). Hash maps also use more memory per element than arrays (storing the hash table structure itself), and they don't preserve insertion order by default in many languages (though Python's dict and JavaScript's Map do preserve insertion order as of recent language versions, which is itself a common trivia question).

Category 2: Arrays and Strings (Q9-Q16)

Array and string problems are the bread and butter of coding interviews. These eight questions cover the most commonly asked problems and the patterns (hash map lookups, prefix/suffix, sliding window, two pointers) that solve the majority of array-based interview problems.

Q9. Two Sum: given an array and a target, find two numbers that add up to the target.

This is the single most commonly asked coding interview question across every company tier. The pattern: hash map for O(1) complement lookups.

python
def two_sum(nums, target):
    seen = {}  # value -> index
    for i, num in enumerate(nums):
        complement = target - num
        if complement in seen:
            return [seen[complement], i]
        seen[num] = i
    return []

# Example: two_sum([2, 7, 11, 15], 9) -> [0, 1]  (2 + 7 = 9)

Time: O(n). Space: O(n).

The brute-force O(n²) approach checks every pair with nested loops. The hash map approach is the expected "optimized" answer: as you iterate, you check whether the complement of the current number has already been seen, which turns the nested-loop search into a single pass with O(1) lookups.

Q10. Maximum Subarray (Kadane's Algorithm): find the contiguous subarray with the largest sum.

python
def max_subarray(nums):
    max_sum = current_sum = nums[0]
    for num in nums[1:]:
        current_sum = max(num, current_sum + num)
        max_sum = max(max_sum, current_sum)
    return max_sum

# Example: max_subarray([-2, 1, -3, 4, -1, 2, 1, -5, 4]) -> 6  ([4, -1, 2, 1])

Time: O(n). Space: O(1).

The key insight (Kadane's algorithm): at each position, decide whether extending the current subarray (current_sum + num) is better than starting fresh from the current element alone (num). If current_sum ever goes negative, starting fresh is always at least as good, so the running sum effectively "resets" itself through the max() comparison without needing an explicit reset condition.

Q11. Product of Array Except Self: return an array where each element is the product of all other elements, without using division.

python
def product_except_self(nums):
    n = len(nums)
    result = [1] * n

    prefix = 1
    for i in range(n):
        result[i] = prefix
        prefix *= nums[i]

    suffix = 1
    for i in range(n - 1, -1, -1):
        result[i] *= suffix
        suffix *= nums[i]

    return result

# Example: product_except_self([1, 2, 3, 4]) -> [24, 12, 8, 6]

Time: O(n). Space: O(1) extra (excluding the output array itself).

The pattern: prefix and suffix products. The first pass computes, for each index, the product of all elements to its LEFT. The second pass multiplies in the product of all elements to its RIGHT. Combined, each position ends up with the product of everything except itself, without ever dividing.

Q12. Merge Intervals: given a collection of intervals, merge all overlapping ones.

python
def merge_intervals(intervals):
    if not intervals:
        return []
    intervals.sort(key=lambda x: x[0])  # sort by start time
    merged = [intervals[0]]

    for current in intervals[1:]:
        last = merged[-1]
        if current[0] <= last[1]:  # overlaps with the last merged interval
            last[1] = max(last[1], current[1])
        else:
            merged.append(current)

    return merged

# Example: merge_intervals([[1,3],[2,6],[8,10],[15,18]]) -> [[1,6],[8,10],[15,18]]

Time: O(n log n), dominated by the sort. Space: O(n) for the result.

The pattern: sort first, then a single linear pass. Sorting by start time guarantees that any interval that could possibly overlap with the current merged interval will be checked immediately next, since intervals further along in the sorted order start even later.

Q13. Longest Substring Without Repeating Characters.

python
def longest_unique_substring(s):
    seen = set()
    left = 0
    max_length = 0

    for right in range(len(s)):
        while s[right] in seen:
            seen.remove(s[left])
            left += 1
        seen.add(s[right])
        max_length = max(max_length, right - left + 1)

    return max_length

# Example: longest_unique_substring("abcabcbb") -> 3  ("abc")

Time: O(n). Space: O(min(n, charset size)).

The pattern: sliding window. Expand the window by moving right forward. When a repeat is found, shrink the window from the left until the repeat is removed, then continue expanding. This avoids re-checking substrings from scratch, which is what makes the brute force O(n³) approach collapse down to O(n).

Q14. Valid Anagram: determine if two strings are anagrams of each other.

python
def is_anagram(s, t):
    if len(s) != len(t):
        return False
    char_count = {}
    for char in s:
        char_count[char] = char_count.get(char, 0) + 1
    for char in t:
        if char not in char_count:
            return False
        char_count[char] -= 1
        if char_count[char] == 0:
            del char_count[char]
    return len(char_count) == 0

# Example: is_anagram("anagram", "nagaram") -> True

Time: O(n). Space: O(1) for fixed alphabet size (or O(k) for k distinct characters).

The pattern: frequency counting with a hash map. An alternative O(n log n) solution sorts both strings and compares them directly, which is simpler to write but asymptotically slower, worth mentioning to show you know the optimized approach exists.

Q15. Move Zeroes: move all zeroes to the end while maintaining relative order of non-zero elements, in place.

python
def move_zeroes(nums):
    insert_pos = 0
    for num in nums:
        if num != 0:
            nums[insert_pos] = num
            insert_pos += 1
    for i in range(insert_pos, len(nums)):
        nums[i] = 0

# Example: nums = [0, 1, 0, 3, 12]; move_zeroes(nums) -> [1, 3, 12, 0, 0]

Time: O(n). Space: O(1).

The pattern: two pointers, one tracking where the next non-zero element should be placed (insert_pos), one scanning through the array. This is a common variant of the broader "partition in place" pattern, the same underlying idea used in the Dutch national flag problem and certain quicksort partition steps.

Q16. Rotate Array: rotate an array to the right by k steps, in place.

python
def rotate(nums, k):
    n = len(nums)
    k = k % n  # handle k larger than array length

    def reverse(start, end):
        while start < end:
            nums[start], nums[end] = nums[end], nums[start]
            start += 1
            end -= 1

    reverse(0, n - 1)        # reverse the whole array
    reverse(0, k - 1)        # reverse the first k elements
    reverse(k, n - 1)        # reverse the remaining elements

# Example: nums = [1,2,3,4,5,6,7], k=3 -> [5,6,7,1,2,3,4]

Time: O(n). Space: O(1).

The pattern: reverse-based rotation. Reversing the entire array, then reversing each of the two resulting segments separately, produces the rotated result without any extra array allocation. It's a favorite "do you know a clever trick beyond the obvious approach" follow-up after a candidate first proposes the simpler O(n) extra-space solution.

Category 3: Linked Lists, Stacks, and Queues (Q17-Q23)

Linked list problems test pointer manipulation skills, and stack/queue problems test whether you understand LIFO and FIFO behavior. The fast/slow pointer pattern introduced here shows up across multiple seemingly unrelated problems.

Q17. Reverse a Linked List.

python
class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

def reverse_linked_list(head):
    prev = None
    current = head
    while current:
        next_node = current.next  # save the next node before overwriting
        current.next = prev       # reverse the pointer
        prev = current
        current = next_node
    return prev  # prev is now the new head

Time: O(n). Space: O(1).

This is one of the most fundamental linked list questions and a building block for many more complex linked list problems (reverse in groups of k, reverse a sublist, palindrome checks). The key insight: you need to save next before overwriting current.next, otherwise you lose your way forward through the list.

Q18. Detect a Cycle in a Linked List (Floyd's Cycle Detection).

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

Time: O(n). Space: O(1).

The pattern: fast and slow pointers (also called the tortoise and hare algorithm). The slow pointer moves one step at a time, the fast pointer moves two steps. If there is a cycle, the fast pointer will eventually lap the slow pointer and they will meet inside the cycle. If there's no cycle, the fast pointer reaches the end (None) first.

This is a top-tier interview pattern specifically because it solves the problem in O(1) space, compared to the more obvious O(n) space solution using a hash set to track visited nodes. Naming both approaches and explaining why the pointer trick wins on space is a strong answer.

Q19. Merge Two Sorted Linked Lists.

python
def merge_two_lists(l1, l2):
    dummy = ListNode()
    current = dummy

    while l1 and l2:
        if l1.val <= l2.val:
            current.next = l1
            l1 = l1.next
        else:
            current.next = l2
            l2 = l2.next
        current = current.next

    current.next = l1 if l1 else l2  # attach whichever list has leftovers
    return dummy.next

Time: O(n + m). Space: O(1) extra (reusing existing nodes).

The dummy node pattern shown here (a placeholder head node that gets discarded at the end via dummy.next) is a common technique that simplifies linked list code by avoiding special-case handling for the very first node.

Q20. Valid Parentheses: determine if a string of brackets is properly balanced.

python
def is_valid(s):
    stack = []
    pairs = {')': '(', ']': '[', '}': '{'}

    for char in s:
        if char in '([{':
            stack.append(char)
        elif char in pairs:
            if not stack or stack.pop() != pairs[char]:
                return False

    return len(stack) == 0

# Example: is_valid("({[]})") -> True
# Example: is_valid("(]") -> False

Time: O(n). Space: O(n) for the stack in the worst case.

The pattern: stack-based matching. Every opening bracket gets pushed. Every closing bracket must match the most recently pushed opening bracket (LIFO order), which is exactly the behavior a stack provides natively. This pattern generalizes to any "matching pairs in nested order" problem.

Q21. Design an LRU (Least Recently Used) Cache.

python
from collections import OrderedDict

class LRUCache:
    def __init__(self, capacity):
        self.cache = OrderedDict()
        self.capacity = capacity

    def get(self, key):
        if key not in self.cache:
            return -1
        self.cache.move_to_end(key)  # mark as recently used
        return self.cache[key]

    def put(self, key, value):
        if key in self.cache:
            self.cache.move_to_end(key)
        self.cache[key] = value
        if len(self.cache) > self.capacity:
            self.cache.popitem(last=False)  # evict the least recently used

Time: O(1) for both get and put. Space: O(capacity).

This is a frequently asked "design" question testing whether you know to combine a hash map (for O(1) key lookup) with a doubly linked list (for O(1) reordering to track recency), or know that your language's standard library already provides this combination (Python's OrderedDict). Many interviewers expect you to implement the doubly linked list + hash map combination manually, so be ready to explain (or implement) that version if asked to go deeper.

Q22. Implement a Queue using two Stacks.

python
class QueueUsingStacks:
    def __init__(self):
        self.stack1 = []  # for enqueue
        self.stack2 = []  # for dequeue

    def enqueue(self, x):
        self.stack1.append(x)

    def dequeue(self):
        if not self.stack2:
            while self.stack1:
                self.stack2.append(self.stack1.pop())  # reverse order
        return self.stack2.pop()

Time: O(1) amortized for both operations. Space: O(n).

This question tests understanding of how LIFO and FIFO behaviors relate. Moving every element from stack1 to stack2 reverses their order once, which converts the LIFO behavior of a single stack into the FIFO behavior a queue needs. The amortized analysis (Q7) applies directly here: most dequeues are O(1) because stack2 already has elements ready, with the occasional O(n) transfer when stack2 empties out.

Q23. Find the Middle of a Linked List in one pass.

python
def find_middle(head):
    slow = fast = head
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
    return slow  # slow is now at the middle

Time: O(n). Space: O(1).

This reuses the same fast/slow pointer pattern from cycle detection (Q18). The fast pointer moves twice as fast as the slow pointer, so by the time the fast pointer reaches the end, the slow pointer has covered exactly half the distance. This is a strong example of how learning one pattern (fast/slow pointers) unlocks multiple seemingly unrelated problems.

Category 4: Trees and Graphs (Q24-Q32)

Tree and graph problems test recursive thinking and traversal strategy selection. Recognizing that a 2D grid problem is secretly a graph connectivity problem (Q28) or that a dependency problem is secretly cycle detection (Q29) is the kind of pattern recognition that separates strong candidates.

Q24. What are the three types of binary tree traversal and how do they differ?

In-order visits left subtree, then the node, then right subtree. For a BST, in-order visits nodes in sorted ascending order.

Pre-order visits the node first, then left subtree, then right subtree. Useful for creating a copy of a tree.

Post-order visits left subtree, then right subtree, then the node. Useful for deleting a tree safely (children before parent).

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

def inorder(root):
    if not root:
        return []
    return inorder(root.left) + [root.val] + inorder(root.right)

def preorder(root):
    if not root:
        return []
    return [root.val] + preorder(root.left) + preorder(root.right)

def postorder(root):
    if not root:
        return []
    return postorder(root.left) + postorder(root.right) + [root.val]

All three run in O(n) time and O(h) space for the recursion stack, where h is the tree height (O(log n) for a balanced tree, O(n) for a completely skewed tree, which is itself a common follow-up about worst-case space).

Q25. What is the difference between BFS and DFS, and when do you use each?

Breadth-First Search (BFS) explores all neighbors of a node before moving to the next level, using a queue. It explores level by level, outward from the starting point.

Depth-First Search (DFS) explores as far as possible down one branch before backtracking, using a stack (or recursion, which uses the call stack implicitly).

python
from collections import deque

def bfs(root):
    if not root:
        return []
    result, queue = [], deque([root])
    while queue:
        node = queue.popleft()
        result.append(node.val)
        if node.left: queue.append(node.left)
        if node.right: queue.append(node.right)
    return result

def dfs(root):
    if not root:
        return []
    result, stack = [], [root]
    while stack:
        node = stack.pop()
        result.append(node.val)
        if node.right: stack.append(node.right)
        if node.left: stack.append(node.left)
    return result

Use BFS when you need the shortest path in an unweighted graph, or level-by-level processing (binary tree level order traversal, finding the minimum number of steps to reach a target).

Use DFS when you need to explore all possible paths, detect cycles, solve connectivity problems, or when the problem is naturally recursive (tree traversals, backtracking problems).

Q26. Validate a Binary Search Tree.

python
def is_valid_bst(root, lower=float('-inf'), upper=float('inf')):
    if not root:
        return True
    if not (lower < root.val < upper):
        return False
    return (is_valid_bst(root.left, lower, root.val) and
            is_valid_bst(root.right, root.val, upper))

Time: O(n). Space: O(h) for the recursion stack.

The common mistake candidates make: checking only that <code>root.left.val < root.val < root.right.val</code> at each node locally, without tracking the valid RANGE inherited from ancestors. A node deep in the left subtree must be less than EVERY ancestor it's a left descendant of, not just its immediate parent. The recursive range-passing approach shown here handles this correctly.

Q27. Find the Lowest Common Ancestor of two nodes in a Binary Tree.

python
def lowest_common_ancestor(root, p, q):
    if not root or root == p or root == q:
        return root

    left = lowest_common_ancestor(root.left, p, q)
    right = lowest_common_ancestor(root.right, p, q)

    if left and right:
        return root  # p and q found in different subtrees, root is the LCA
    return left if left else right

Time: O(n). Space: O(h).

The pattern: post-order recursion that "bubbles up" findings. Each recursive call returns either None (neither target found), one of the target nodes (found exactly one), or the LCA itself (both targets found in different branches below). The genuinely clever part: if a node's left and right recursive calls both return non-None, that node IS the LCA.

Q28. Number of Islands: count the number of connected groups of land cells in a grid.

python
def num_islands(grid):
    if not grid:
        return 0

    def dfs(i, j):
        if (i < 0 or i >= len(grid) or j < 0 or j >= len(grid[0])
                or grid[i][j] == '0'):
            return
        grid[i][j] = '0'  # mark as visited by sinking the island cell
        dfs(i + 1, j)
        dfs(i - 1, j)
        dfs(i, j + 1)
        dfs(i, j - 1)

    count = 0
    for i in range(len(grid)):
        for j in range(len(grid[0])):
            if grid[i][j] == '1':
                count += 1
                dfs(i, j)  # sink the entire connected island

    return count

Time: O(rows cols). Space: O(rows cols) worst case for the recursion stack.

This is the canonical "grid as implicit graph" problem. Each cell is a node, and adjacency is implicit (up/down/left/right neighbors) rather than an explicit adjacency list. Recognizing that a 2D grid traversal problem is secretly a graph connectivity problem is a key pattern-recognition skill this question specifically tests.

Q29. Course Schedule: given course prerequisites, determine if it's possible to finish all courses.

python
def can_finish(num_courses, prerequisites):
    graph = {i: [] for i in range(num_courses)}
    for course, prereq in prerequisites:
        graph[course].append(prereq)

    visiting, visited = set(), set()

    def has_cycle(course):
        if course in visiting:
            return True  # found a cycle
        if course in visited:
            return False  # already fully processed, no cycle
        visiting.add(course)
        for prereq in graph[course]:
            if has_cycle(prereq):
                return True
        visiting.remove(course)
        visited.add(course)
        return False

    return not any(has_cycle(course) for course in range(num_courses))

Time: O(V + E). Space: O(V + E) for the graph plus O(V) for the recursion stack.

This is the classic real-world application of topological sort and cycle detection in a directed graph: if course A requires course B, and course B (eventually) requires course A, the schedule is impossible because of the circular dependency. The visiting set (currently in the recursion path) versus visited set (fully processed, confirmed cycle-free) distinction is the key technique for detecting cycles in directed graphs specifically.

Q30. Maximum Depth of a Binary Tree.

python
def max_depth(root):
    if not root:
        return 0
    return 1 + max(max_depth(root.left), max_depth(root.right))

Time: O(n). Space: O(h).

This looks trivial, and it largely is, but it's frequently asked as a warm-up question to establish baseline recursive thinking. The recursive structure here (base case: empty tree has depth 0; recursive case: 1 plus the deeper of the two child subtrees) is the template that nearly every tree depth/height/balance problem builds on.

Q31. Determine if two Binary Trees are the same (identical structure and values).

python
def is_same_tree(p, q):
    if not p and not q:
        return True
    if not p or not q:
        return False
    if p.val != q.val:
        return False
    return is_same_tree(p.left, q.left) and is_same_tree(p.right, q.right)

Time: O(n). Space: O(h).

Three base cases to get right: both null (match), exactly one null (no match), and value mismatch (no match). Many candidates forget the "exactly one is null" case and write code that crashes with a null pointer exception, which is itself a useful thing to watch for during an interview.

Q32. What is the difference between an adjacency list and an adjacency matrix for representing a graph?

python
# Adjacency matrix for a 4-node graph
graph_matrix = [
    [0, 1, 1, 0],
    [1, 0, 0, 1],
    [1, 0, 0, 1],
    [0, 1, 1, 0],
]

# Adjacency list for the same graph
graph_list = {
    0: [1, 2],
    1: [0, 3],
    2: [0, 3],
    3: [1, 2],
}
Adjacency MatrixAdjacency List
SpaceO(V²)O(V + E)
Check if edge existsO(1)O(degree of node)
Iterate neighborsO(V)O(degree of node)
Best forDense graphsSparse graphs (most real-world graphs)

Adjacency lists are the default choice for most real-world graphs, which tend to be sparse (relatively few edges compared to V² possible edges). Adjacency matrices make sense when the graph is dense or when O(1) edge-existence checks matter more than memory efficiency.

Category 5: Coding Patterns Reference (Q33-Q40)

This is the highest-leverage section of the entire guide. Learning these 8 patterns covers the vast majority of coding interview problems. Each entry describes the pattern, names the signals that indicate when to use it, and points back to the concrete problem earlier in this guide that demonstrates it.

Q33. What is the Two Pointers pattern and what signals indicate you should use it?

Use two indices that move through a structure based on specific conditions, typically reducing an O(n²) brute-force approach to O(n).

Signals: sorted arrays, finding pairs that satisfy a condition, palindrome checks, or removing duplicates in place.

python
def two_sum_sorted(arr, target):  # arr is SORTED
    left, right = 0, len(arr) - 1
    while left < right:
        current = arr[left] + arr[right]
        if current == target:
            return [left, right]
        elif current < target:
            left += 1   # need a bigger sum, move left pointer up
        else:
            right -= 1  # need a smaller sum, move right pointer down
    return []

This is distinct from the hash-map Two Sum (Q9), which works on unsorted arrays. When the array is already sorted, two pointers achieves the same O(n) time with O(1) space instead of O(n) space, which is a common "can you do it without extra space" follow-up.

Q34. What is the Sliding Window pattern and what signals indicate you should use it?

Maintain a "window" (a contiguous range defined by two pointers) over an array or string, expanding or shrinking it based on problem conditions, rather than recomputing from scratch for every possible subarray.

Signals: contiguous subarrays/substrings, problems mentioning "consecutive elements," finding the longest/shortest substring or subarray satisfying a condition.

Already demonstrated in Q13 (Longest Substring Without Repeating Characters). The pattern avoids the brute-force approach of checking every possible window from scratch (typically O(n²) or O(n³)), instead reusing work as the window slides, getting down to O(n).

Q35. What is the Fast and Slow Pointers pattern and what signals indicate you should use it?

Use two pointers moving at different speeds through a structure (usually a linked list), commonly one step versus two steps per iteration.

Signals: cycle detection, finding the middle element, detecting palindromic structure in a linked list.

Already demonstrated in Q18 (cycle detection) and Q23 (finding the middle). The key insight that unifies both use cases: the speed difference between the two pointers creates a predictable mathematical relationship (the fast pointer covers exactly twice the distance) that you can exploit without needing extra memory to track positions explicitly.

Q36. What is the Backtracking pattern and what signals indicate you should use it?

Explore all possible solutions by making a choice, recursing into the consequences of that choice, and undoing the choice (backtracking) if it doesn't lead anywhere, to try the next option.

Signals: "generate all combinations/permutations," "find all possible ways to," constraint satisfaction problems (N-Queens, Sudoku), or exhaustive search where you need every valid solution.

python
def subsets(nums):
    result = []

    def backtrack(start, current):
        result.append(current[:])  # record a copy of the current combination
        for i in range(start, len(nums)):
            current.append(nums[i])      # make a choice
            backtrack(i + 1, current)    # recurse
            current.pop()                # undo the choice (backtrack)

    backtrack(0, [])
    return result

# Example: subsets([1, 2, 3]) -> [[], [1], [1,2], [1,2,3], [1,3], [2], [2,3], [3]]

Time: O(2^n) for subsets specifically. Space: O(n) for the recursion depth.

The append-recurse-pop sequence is the universal backtracking template. The pop() after the recursive call is what makes it backtracking rather than just plain recursion: it explicitly undoes the choice before trying the next option in the loop.

Q37. What are common variants of Binary Search beyond the basic version?

Beyond standard binary search (Q3), several common variants come up specifically because they require adapting the basic template rather than applying it directly. Search in a rotated sorted array:

python
def search_rotated(nums, target):
    left, right = 0, len(nums) - 1
    while left <= right:
        mid = (left + right) // 2
        if nums[mid] == target:
            return mid
        if nums[left] <= nums[mid]:  # left half is sorted
            if nums[left] <= target < nums[mid]:
                right = mid - 1
            else:
                left = mid + 1
        else:  # right half is sorted
            if nums[mid] < target <= nums[right]:
                left = mid + 1
            else:
                right = mid - 1
    return -1

Time: O(log n). The key insight: at each midpoint, one half of the array is guaranteed to be properly sorted (even though the whole array isn't). Determine which half is sorted, then decide whether the target could lie in that sorted half or must be in the other half.

The unifying signal across all binary search variants: any time you can eliminate half the remaining search space based on a comparison, binary search (or a variant of it) is probably the right tool, even when the input isn't obviously "search for X in a sorted array."

Q38. What is Topological Sort and what signals indicate you should use it?

Produce a linear ordering of nodes in a directed acyclic graph (DAG) such that for every directed edge from A to B, A comes before B in the ordering.

Signals: dependency ordering, task scheduling, build systems, course prerequisites (already shown in Q29), or any problem describing a DAG ordering constraint.

python
from collections import deque

def topological_sort(num_nodes, edges):
    graph = {i: [] for i in range(num_nodes)}
    in_degree = {i: 0 for i in range(num_nodes)}

    for src, dst in edges:
        graph[src].append(dst)
        in_degree[dst] += 1

    queue = deque([node for node in range(num_nodes) if in_degree[node] == 0])
    result = []

    while queue:
        node = queue.popleft()
        result.append(node)
        for neighbor in graph[node]:
            in_degree[neighbor] -= 1
            if in_degree[neighbor] == 0:
                queue.append(neighbor)

    return result if len(result) == num_nodes else []  # empty if a cycle exists

Time: O(V + E). Space: O(V + E).

This uses Kahn's algorithm (BFS-based): start with nodes that have no incoming edges (in-degree 0), process them, decrement the in-degree of their neighbors, and add any neighbor that reaches in-degree 0 to the queue. If the final result doesn't include every node, a cycle exists.

Q39. What is a Monotonic Stack and what signals indicate you should use it?

A monotonic stack maintains elements in either strictly increasing or strictly decreasing order, popping elements that violate the monotonic property before pushing a new element.

Signals: "next greater element," "daily temperatures," problems asking for the nearest element satisfying a comparison in a specific direction.

python
def next_greater_element(nums):
    result = [-1] * len(nums)
    stack = []  # stores indices, maintained in decreasing value order

    for i, num in enumerate(nums):
        while stack and nums[stack[-1]] < num:
            result[stack.pop()] = num  # found the "next greater" for this index
        stack.append(i)

    return result

# Example: next_greater_element([2, 1, 2, 4, 3]) -> [4, 2, 4, -1, -1]

Time: O(n). Space: O(n).

The naive brute force is O(n²): for each element, scan forward until you find something bigger. The monotonic stack gets this down to O(n) because each element is pushed and popped at most once.

Q40. What is Union-Find (Disjoint Set Union) and what signals indicate you should use it?

Union-Find efficiently tracks which elements belong to which connected group (set) and supports two operations: union (merge two groups) and find (determine which group an element belongs to), both close to O(1) with path compression.

Signals: counting connected components, detecting cycles in an undirected graph, problems about grouping/merging entities based on relationships (friend circles, network connectivity).

python
class UnionFind:
    def __init__(self, n):
        self.parent = list(range(n))

    def find(self, x):
        if self.parent[x] != x:
            self.parent[x] = self.find(self.parent[x])  # path compression
        return self.parent[x]

    def union(self, x, y):
        root_x, root_y = self.find(x), self.find(y)
        if root_x != root_y:
            self.parent[root_x] = root_y
            return True  # a union happened (different groups)
        return False  # already in the same group

Time: nearly O(1) amortized per operation with path compression. Space: O(n).

Union-Find is often the cleanest solution for "are these two things connected" problems, frequently outperforming repeated BFS/DFS when you need to answer many connectivity queries interspersed with updates to the graph structure over time.

Category 6: Dynamic Programming and Greedy (Q41-Q47)

Dynamic programming is where mid-level and senior candidates get separated. The key is recognizing when a problem has optimal substructure and overlapping subproblems, then deciding between top-down (memoization) and bottom-up (tabulation) approaches.

Q41. What is Dynamic Programming and when should you reach for it?

Dynamic Programming (DP) solves complex problems by breaking them into smaller overlapping subproblems, solving each subproblem once, and storing (memoizing) the result to avoid redundant recomputation.

Two conditions signal a DP problem: optimal substructure (the optimal solution can be built from optimal solutions to its subproblems) and overlapping subproblems (the same subproblems get solved repeatedly in a naive recursive approach).

python
# Naive recursive Fibonacci: O(2^n), exponential
def fib_naive(n):
    if n <= 1:
        return n
    return fib_naive(n - 1) + fib_naive(n - 2)

# DP with memoization: O(n), each value computed exactly once
def fib_memo(n, memo={}):
    if n in memo:
        return memo[n]
    if n <= 1:
        return n
    memo[n] = fib_memo(n - 1, memo) + fib_memo(n - 2, memo)
    return memo[n]

Signals in a problem statement: "find the maximum/minimum," "count the number of ways," or "is it possible to," combined with a sense that smaller versions of the same problem feed into the answer for larger versions.

Q42. Climbing Stairs: count the number of distinct ways to climb n stairs, taking 1 or 2 steps at a time.

python
def climb_stairs(n):
    if n <= 2:
        return n
    prev2, prev1 = 1, 2
    for _ in range(3, n + 1):
        current = prev1 + prev2
        prev2, prev1 = prev1, current
    return prev1

# Example: climb_stairs(5) -> 8

Time: O(n). Space: O(1).

This is structurally identical to Fibonacci (Q41): the number of ways to reach step n equals the ways to reach step n-1 plus the ways to reach step n-2. Recognizing "this is secretly Fibonacci" is itself a useful pattern-matching skill.

Q43. Coin Change: find the minimum number of coins needed to make a given amount.

python
def coin_change(coins, amount):
    dp = [float('inf')] * (amount + 1)
    dp[0] = 0  # base case: 0 coins needed to make amount 0

    for current_amount in range(1, amount + 1):
        for coin in coins:
            if coin <= current_amount:
                dp[current_amount] = min(dp[current_amount],
                                          dp[current_amount - coin] + 1)

    return dp[amount] if dp[amount] != float('inf') else -1

# Example: coin_change([1, 2, 5], 11) -> 3  (5 + 5 + 1)

Time: O(amount * len(coins)). Space: O(amount).

The DP array dp[i] stores the minimum coins needed to make amount i. For each amount, try every coin denomination that fits, and take the best result from "1 coin plus however many coins were needed for the remainder." This bottom-up (tabulation) style fills the array from smaller amounts up to the target.

Q44. Longest Common Subsequence (LCS) between two strings.

python
def longest_common_subsequence(text1, text2):
    m, n = len(text1), len(text2)
    dp = [[0] * (n + 1) for _ in range(m + 1)]

    for i in range(1, m + 1):
        for j in range(1, n + 1):
            if text1[i - 1] == text2[j - 1]:
                dp[i][j] = dp[i - 1][j - 1] + 1
            else:
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])

    return dp[m][n]

# Example: longest_common_subsequence("abcde", "ace") -> 3  ("ace")

Time: O(m n). Space: O(m n).

This is the canonical 2D DP problem. dp[i][j] represents the LCS length considering the first i characters of text1 and the first j characters of text2. If the characters match, extend by 1. If they don't, take the best achievable by dropping one character from either string. This same 2D DP table structure underlies many string-comparison problems (edit distance, longest common substring).

Q45. 0/1 Knapsack: maximize value within a weight capacity, where each item can be used at most once.

python
def knapsack(weights, values, capacity):
    n = len(weights)
    dp = [[0] * (capacity + 1) for _ in range(n + 1)]

    for i in range(1, n + 1):
        for w in range(capacity + 1):
            if weights[i - 1] <= w:
                dp[i][w] = max(
                    dp[i - 1][w],  # don't take item i-1
                    dp[i - 1][w - weights[i - 1]] + values[i - 1]  # take it
                )
            else:
                dp[i][w] = dp[i - 1][w]  # can't fit, skip it

    return dp[n][capacity]

# Example: knapsack([1,3,4,5], [1,4,5,7], 7) -> 9

Time: O(n capacity). Space: O(n capacity).

For each item, you choose to either include it (and gain its value while using up its weight) or exclude it (keeping the previous best for the same capacity). This is the foundational pattern behind a huge family of "subset selection with a constraint" problems.

Q46. House Robber: maximize the sum of non-adjacent elements in an array.

python
def rob(nums):
    if not nums:
        return 0
    prev2, prev1 = 0, 0
    for num in nums:
        current = max(prev1, prev2 + num)  # skip this house, or rob it
        prev2, prev1 = prev1, current
    return prev1

# Example: rob([2, 7, 9, 3, 1]) -> 12  (2 + 9 + 1)

Time: O(n). Space: O(1).

At each house, you decide: skip it (carry forward the best result so far, prev1) or rob it (take its value plus the best result from two houses back, prev2 + num, since you can't rob adjacent houses). This is a simpler relative of Knapsack (Q45) with an implicit "adjacency" constraint instead of a weight constraint.

Q47. When does a Greedy approach fail where Dynamic Programming succeeds?

A greedy algorithm makes the locally optimal choice at each step, hoping it leads to a globally optimal solution. This works for some problems (like coin change with standard US denominations: 1, 5, 10, 25) but fails for others where local optimality doesn't guarantee global optimality.

The classic counter-example: Coin Change (Q43) with coins [1, 3, 4] and target 6. A greedy approach takes the largest coin first: 4, leaving 2, then two 1-coins, for a total of 3 coins (4+1+1). But the optimal answer is 2 coins (3+3).

python
# Greedy approach: WRONG for non-standard coin sets
def greedy_coin_change(coins, amount):
    coins.sort(reverse=True)
    count = 0
    for coin in coins:
        count += amount // coin
        amount %= coin
    return count if amount == 0 else -1
# greedy_coin_change([1, 3, 4], 6) -> 3 (WRONG, optimal is 2)

# DP approach from Q43 correctly returns 2

Greedy is faster (usually O(n) or O(n log n)) and simpler when it works, but it only works when the problem has the "greedy choice property," where a locally optimal choice never prevents reaching the globally optimal solution. DP is the safer, more broadly correct tool when you're not certain that property holds, at the cost of higher time/space complexity. Being able to identify specifically WHY greedy fails on a counter-example is what separates a strong answer here.

Category 7: Sorting, Searching, and Wrap-Up (Q48-Q50)

This final category covers sorting algorithm trade-offs, advanced binary search variants, and the meta-question about what mistakes to avoid during the interview itself.

Q48. Compare common sorting algorithms and when you'd choose each.

AlgorithmTime (average)Time (worst)SpaceStable?Notes
Bubble SortO(n²)O(n²)O(1)YesRarely used in practice, mainly educational
Insertion SortO(n²)O(n²)O(1)YesFast for small or nearly-sorted arrays
Merge SortO(n log n)O(n log n)O(n)YesPredictable, good for linked lists
Quick SortO(n log n)O(n²)O(log n)NoFast in practice, worst case rare with good pivot
Heap SortO(n log n)O(n log n)O(1)NoIn-place, but worse cache performance

Most languages' built-in sort functions use a hybrid approach (Python's Timsort, a hybrid of merge sort and insertion sort). "Stable" matters when you need to preserve the relative order of equal elements, which comes up in multi-key sorting.

A common follow-up: "why does quicksort have a worst case of O(n²) despite being fast in practice?" Answer: a poor pivot choice (always picking the first or last element on an already-sorted array) degenerates into O(n²). Randomized pivot selection or median-of-three pivot selection mitigates this in practice.

Q49. What are the variants of Binary Search you should know beyond finding an exact match?

Building on Q37, two other binary search variants come up consistently. Finding the first/last occurrence in a sorted array with duplicates:

python
def find_first_occurrence(arr, target):
    left, right = 0, len(arr) - 1
    result = -1
    while left <= right:
        mid = (left + right) // 2
        if arr[mid] == target:
            result = mid
            right = mid - 1  # keep searching LEFT for an earlier occurrence
        elif arr[mid] < target:
            left = mid + 1
        else:
            right = mid - 1
    return result

Binary search on the answer space (rather than searching an actual array): used for problems like "find the minimum capacity needed" or "find the square root," where you binary search over a RANGE OF POSSIBLE ANSWERS, checking at each midpoint whether that candidate answer satisfies the problem's constraints.

Recognizing when a problem is "secretly" a binary search on the answer space, rather than a search through an explicit array, is a more advanced pattern-recognition skill that separates strong mid-level candidates.

Q50. What are the most common mistakes candidates make in DSA interviews?

  • Jumping straight to code without clarifying the problem. Strong candidates ask about edge cases (empty input, duplicates, negative numbers), confirm input constraints, and restate the problem in their own words before writing a single line.
  • Defaulting to brute force and stopping there. A correct O(n²) solution when an O(n) or O(n log n) solution exists is usually treated as an incomplete answer. Always verbally flag the brute-force complexity, then work toward an optimized approach.
  • Not stating time/space complexity explicitly. Interviewers expect you to state it unprompted as part of explaining your solution. Silence on this point reads as a gap in fundamentals.
  • Not testing the solution against edge cases out loud. Walking through your own code with an empty input, a single-element input, or all-duplicates, and catching your own bugs before the interviewer does, is one of the strongest positive signals you can send.
  • Memorizing solutions instead of understanding patterns. A candidate who can't adapt the Two Sum solution when the interviewer changes the problem slightly (three numbers instead of two, or a sorted array) reveals that the underlying pattern was never actually understood.

Quick Reference: All 50 Questions at a Glance

#QuestionCore Concept
Q1What is a data structureOrganization for efficient access/modification
Q2Linear vs non-linear data structuresSequential vs hierarchical/network
Q3Big-O notationGrowth rate, worst-case, O(1) through O(n!)
Q4Time vs space complexityRunning time growth vs memory growth
Q5ADT vs data structureContract (Stack) vs implementation (array/linked list)
Q6Arrays vs linked listsO(1) access vs O(1) insert/delete, cache locality
Q7Amortized time complexityDynamic array doubling, average over many ops
Q8Hash map vs arrayO(1) average lookup vs collision worst case
Q9Two SumHash map complement lookup, O(n)
Q10Maximum Subarray (Kadane's)Running sum reset via max(), O(n)
Q11Product of Array Except SelfPrefix and suffix products, O(n)
Q12Merge IntervalsSort by start, single pass merge, O(n log n)
Q13Longest Substring Without RepeatingSliding window with seen set, O(n)
Q14Valid AnagramFrequency counting hash map, O(n)
Q15Move ZeroesTwo pointers in-place partition, O(n)
Q16Rotate ArrayReverse-based rotation trick, O(n) O(1) space
Q17Reverse a Linked ListSave next before overwrite, O(n)
Q18Detect a Cycle (Floyd's)Fast/slow pointers, O(n) O(1) space
Q19Merge Two Sorted ListsDummy node technique, O(n+m)
Q20Valid ParenthesesStack-based bracket matching, O(n)
Q21LRU Cache designHash map + doubly linked list, O(1) ops
Q22Queue using two StacksReverse order via stack transfer, amortized O(1)
Q23Find Middle of Linked ListFast/slow pointers, O(n) O(1) space
Q24Binary tree traversalsIn-order, pre-order, post-order
Q25BFS vs DFSQueue level-by-level vs stack/recursion depth-first
Q26Validate a BSTRange-bound recursion, not just local comparison
Q27Lowest Common AncestorPost-order bubble-up recursion
Q28Number of IslandsGrid as implicit graph, DFS sinking
Q29Course ScheduleDirected graph cycle detection, visiting/visited sets
Q30Maximum Depth of Binary TreeBase recursive template for tree height
Q31Same TreeThree base cases: both null, one null, value mismatch
Q32Adjacency list vs matrixSparse vs dense graph representation
Q33Two Pointers patternSorted arrays, pairs, O(n²) to O(n)
Q34Sliding Window patternContiguous subarrays/substrings
Q35Fast and Slow PointersCycle detection, finding middle
Q36Backtracking patternAppend-recurse-pop template, exhaustive search
Q37Binary Search variantsRotated array, sorted-half detection
Q38Topological SortKahn's algorithm, dependency ordering
Q39Monotonic StackNext greater/smaller element, O(n²) to O(n)
Q40Union-FindConnected components, near-O(1) path compression
Q41What is DPOptimal substructure, overlapping subproblems
Q42Climbing StairsFibonacci-structured intro DP
Q43Coin ChangeBottom-up tabulation, minimum coins
Q44Longest Common Subsequence2D DP table, string comparison foundation
Q450/1 KnapsackInclude/exclude decision per item
Q46House RobberAdjacency-constrained DP, Knapsack relative
Q47When greedy fails vs DPCoin [1,3,4] counter-example
Q48Sorting algorithm comparisonTime/space/stability trade-offs
Q49Binary search beyond exact matchFirst/last occurrence, answer space search
Q50Common DSA interview mistakesClarify first, state complexity, test edges

Frequently Asked Questions

How many LeetCode problems do I need to solve to be interview-ready?

You don't need hundreds. Solving 50 to 75 well-chosen problems that cover the 8 core patterns (two pointers, sliding window, fast/slow pointers, backtracking, binary search variants, topological sort, monotonic stack, union-find) is more effective than grinding 300 random problems. The goal is pattern recognition, not memorization. If you can identify which pattern a new problem maps to within the first few minutes, you're interview-ready.

Which programming language should I use for DSA interviews?

Use whichever language you're most fluent in, unless the job posting specifies one. Python is the most popular choice for DSA interviews because its syntax is concise and its built-in data structures (lists, dicts, sets, deque, heapq) map directly to the abstractions you need. Java is common in enterprise environments and has strong typing that prevents certain classes of bugs. JavaScript works fine but has fewer built-in data structures (no built-in heap, for instance).

The language matters less than fluency. Struggling with syntax while explaining an algorithm is a worse signal than using a "less optimal" language confidently.

How do DSA interviews differ from system design interviews?

DSA interviews test algorithmic problem-solving: given a specific input, produce a specific output as efficiently as possible. The constraint is correctness and time/space complexity. System design interviews test architectural judgment: design a system that handles millions of users, explain the trade-offs between different databases, or describe how you'd scale a specific feature. The constraint is scalability, reliability, and maintainability.

Most companies at mid-level and above run both types. This guide covers DSA. For complementary preparation, see our SQL interview questions for database fundamentals and our OOP interview questions for object-oriented design, which bridges the gap between DSA and system design.

Which coding patterns are most important to learn first?

Start with these three, which cover the highest percentage of interview problems:

  1. Hash map lookups (Q9, Q14): the single most useful technique across all problem types. If your first instinct is nested loops, ask yourself if a hash map can eliminate the inner loop.
  2. Two pointers (Q15, Q33): essential for sorted array problems and in-place modifications.
  3. BFS/DFS (Q25, Q28, Q29): covers tree traversals, graph problems, and grid-based problems in one pattern family.

After those, add sliding window (Q34), backtracking (Q36), and basic dynamic programming (Q41-Q43). These six patterns together cover the vast majority of problems you'll encounter in interviews.

How should I approach a DSA problem I've never seen before?

Follow this sequence: (1) Clarify the problem by restating it in your own words and asking about edge cases and constraints. (2) Work through 1-2 small examples by hand to build intuition. (3) Identify which pattern the problem maps to by looking at the signals described in Q33-Q40. (4) Start with a brute-force approach, state its complexity, then optimize. (5) Code the solution, talking through your decisions as you go. (6) Test with edge cases (empty input, single element, all duplicates) before declaring you're done.

The biggest mistake is skipping steps 1-3 and jumping straight to code. The few minutes spent clarifying and identifying the pattern almost always save time overall.

Dynamic programming feels impossible. How do I get better at it?

Start with the three simplest DP problems in order: Climbing Stairs (Q42, which is just Fibonacci), House Robber (Q46, which adds one constraint to Fibonacci), and Coin Change (Q43, which generalizes to multiple choices per step). These three problems share the same recursive structure, and seeing that structure repeat across different problem statements is the key insight that makes DP click.

For each problem, write the naive recursive solution first, then identify which subproblems get recomputed, then add memoization, then convert to bottom-up tabulation. Going through all four stages (naive, identify overlap, memoize, tabulate) builds the intuition that jumping straight to the DP table never does.

Zeeshan Tofiq

Zeeshan Tofiq

Full Stack Developer

Full stack developer with over 6 years of experience building production applications. Writes practical guides on JavaScript, TypeScript, React, Node.js, and cloud infrastructure. Focused on helping developers solve real problems with clean, maintainable code.

Enjoyed this article?

Get practical dev guides, tool updates, and new articles delivered straight to your inbox. No spam, unsubscribe anytime.

Related Articles

programming

45 OOP Interview Questions and Answers (2026)

45 OOP interview questions covering the four pillars, SOLID principles, and design patterns with real code in Java, Python, JavaScript, and C#. Updated for 2026

Jun 27, 2026·39 min read
databases

40 SQL Interview Questions and Answers (2026)

40 SQL and relational database interview questions covering joins, indexes, ACID, window functions, CTEs, MySQL vs PostgreSQL, and query challenges.

Jun 14, 2026·34 min read
nodejs

30 Node.js Interview Questions and Answers (2026)

30 Node.js interview questions with full answers: event loop, streams, clustering, worker threads, memory leaks, and security. Updated for 2026.

Jun 8, 2026·26 min read

On this page

  • Category 1: Fundamentals and Complexity (Q1-Q8)
  • Q1. What is a data structure and why does choosing the right one matter?
  • Q2. What is the difference between linear and non-linear data structures?
  • Q3. What is Big-O notation and why do interviewers care about it?
  • Q4. What is the difference between time complexity and space complexity?
  • Q5. What is an abstract data type (ADT) and how does it differ from a data structure?
  • Q6. What are the trade-offs between arrays and linked lists?
  • Q7. What is amortized time complexity?
  • Q8. When would you choose a hash map over an array, and what's the catch?
  • Category 2: Arrays and Strings (Q9-Q16)
  • Q9. Two Sum
  • Q10. Maximum Subarray (Kadane's Algorithm)
  • Q11. Product of Array Except Self
  • Q12. Merge Intervals
  • Q13. Longest Substring Without Repeating Characters
  • Q14. Valid Anagram
  • Q15. Move Zeroes
  • Q16. Rotate Array
  • Category 3: Linked Lists, Stacks, and Queues (Q17-Q23)
  • Q17. Reverse a Linked List
  • Q18. Detect a Cycle in a Linked List (Floyd's)
  • Q19. Merge Two Sorted Linked Lists
  • Q20. Valid Parentheses
  • Q21. Design an LRU Cache
  • Q22. Implement a Queue using two Stacks
  • Q23. Find the Middle of a Linked List
  • Category 4: Trees and Graphs (Q24-Q32)
  • Q24. What are the three types of binary tree traversal?
  • Q25. What is the difference between BFS and DFS?
  • Q26. Validate a Binary Search Tree
  • Q27. Find the Lowest Common Ancestor
  • Q28. Number of Islands
  • Q29. Course Schedule (cycle detection)
  • Q30. Maximum Depth of a Binary Tree
  • Q31. Determine if two Binary Trees are the same
  • Q32. Adjacency list vs adjacency matrix
  • Category 5: Coding Patterns Reference (Q33-Q40)
  • Q33. Two Pointers pattern
  • Q34. Sliding Window pattern
  • Q35. Fast and Slow Pointers pattern
  • Q36. Backtracking pattern
  • Q37. Binary Search variants
  • Q38. Topological Sort
  • Q39. Monotonic Stack
  • Q40. Union-Find (Disjoint Set Union)
  • Category 6: Dynamic Programming and Greedy (Q41-Q47)
  • Q41. What is Dynamic Programming and when should you reach for it?
  • Q42. Climbing Stairs
  • Q43. Coin Change
  • Q44. Longest Common Subsequence
  • Q45. 0/1 Knapsack
  • Q46. House Robber
  • Q47. When does a Greedy approach fail where DP succeeds?
  • Category 7: Sorting, Searching, and Wrap-Up (Q48-Q50)
  • Q48. Compare common sorting algorithms
  • Q49. Binary Search variants beyond exact match
  • Q50. Common DSA interview mistakes
  • Quick Reference: All 50 Questions at a Glance
  • Frequently Asked Questions
Advertisement