Python DSA interview toolkit
Use Python's built-ins first, recognize the standard interview patterns, and explain the cost of every solution.
By the end, you’ll be able to…
- Estimate time and space growth before choosing a data structure.
- Use lists, deques, dictionaries, heaps, and bisect for their intended operations.
- Recognize two-pointer, sliding-window, stack, BFS/DFS, and binary-search patterns.
01Measure growth before memorizing code
Big O describes how work grows as the input grows. It is not a stopwatch. A dictionary lookup is O(1) on average, a list membership check is O(n), and sorting is O(n log n). The constant factors still matter in real programs, but the growth rate tells you which approach survives when the input becomes large.
State the cost of the algorithm you write and the extra space it allocates. A solution that copies every string at every step may be correct but expensive. Before optimizing, identify the operation inside the loop that dominates: a nested scan, a repeated list insert at the front, or a dictionary rebuilt for every item. Choose the structure that makes that operation cheap.
02Use the right built-in first
A Python list is a stack: append and pop at the end are O(1) amortized. collections.deque gives O(1) append and popleft for a queue. A dictionary or set provides average O(1) membership and mapping. heapq maintains a priority queue, and bisect finds an insertion point in a sorted list. These tools remove the need to hand-write a linked queue for most interviews.
The cost table is part of the explanation. A set may use more memory than a list but turns a repeated scan into a lookup. A heap is the right choice when you repeatedly need the smallest item, not when you only need to sort once. Use collections.Counter for frequency counting and defaultdict when every missing key should start with a known empty value.
from collections import dequestack = []stack.append(1)stack.append(2)print(stack.pop())queue = deque([1, 2])queue.append(3)print(queue.popleft())seen = {3, 1}print(2 in seen)2 1 False
03Linked lists and trees from scratch
A linked list makes the pointer structure visible even though Python lists are usually the better default. Each node stores a value and a reference to the next node. Reversing a list with three pointers is a classic exercise in preserving the rest of the chain before changing a link. A dataclass keeps the node definition short.
A binary tree node has left and right children. Depth-first traversal uses a stack or recursion; breadth-first traversal uses a queue. For a binary search tree, an in-order traversal visits values in sorted order. State the traversal order and the invariant that makes it useful. Do not assume every binary tree is a search tree.
from dataclasses import dataclass@dataclassclass Node: value: int next: "Node | None" = Nonedef reverse(head): previous = None while head: following = head.next head.next = previous previous = head head = following return previoushead = Node(1, Node(2, Node(3)))current = reverse(head)while current: print(current.value) current = current.next3 2 1
04Patterns for interviews
Two pointers work when a sorted order or a paired boundary lets you discard a range of possibilities. A sliding window works when a contiguous range has a maintainable condition. A hash map stores what you have already seen so a later item can find its complement. A stack is natural for matching delimiters and undoing nested work.
Binary search requires a sorted search space and a predicate that stays true on one side of the boundary. BFS finds the shortest path in an unweighted graph; DFS explores a branch completely and is useful for connectivity and backtracking. Dynamic programming stores overlapping subproblem results. Learn the pattern, then explain why the input's constraints justify it.
def two_sum(values, target): seen = {} for index, value in enumerate(values): need = target - value if need in seen: return (seen[need], index) seen[value] = index return Noneprint(two_sum([2, 7, 11, 15], 9))print(two_sum([1, 2, 3], 100))(0, 1) None
The ideas worth keeping.
- State the growth rate and the extra space before presenting a solution.
- Use the built-in structure that makes the dominant operation cheap.
- Name the pattern, explain why its precondition holds, and test the empty, single-item, and boundary cases.
Make it yours.
Type and test your code right here in the interactive workspace. Run your solution, check the output, and compare with the worked guide when ready.
Validate nested brackets
Write is_balanced(text) using a stack. Accept (), [], and {} and return False for mismatched or unclosed brackets.
Explore a worked solution
def is_balanced(text): pairs = {")": "(", "]": "[", "}": "{"} stack = [] for character in text: if character in "([{": stack.append(character) elif character in pairs: if not stack or stack.pop() != pairs[character]: return False return not stackprint(is_balanced("([{}])"))print(is_balanced("([)]"))print(is_balanced("("))Opening brackets are pushed. A closing bracket must match the most recent unmatched opening, which is exactly the stack's top. The final check catches unclosed opens.
Binary search a sorted list
Implement binary_search(values, target) that returns the index or -1. State the invariant that keeps the search interval correct.
Explore a worked solution
def binary_search(values, target): low, high = 0, len(values) - 1 while low <= high: middle = (low + high) // 2 if values[middle] == target: return middle if values[middle] < target: low = middle + 1 else: high = middle - 1 return -1print(binary_search([1, 3, 5, 7, 9], 7))print(binary_search([1, 3, 5, 7, 9], 4))The invariant is that the target, if present, is inside values[low:high + 1]. Each step removes the half that cannot contain it, so the interval shrinks until it is empty.
What stayed with you?
Check the ideas while they’re fresh. Your answers stay in this page and feedback appears as you go.
Validate nested brackets
Write is_balanced(text) using a stack. Accept (), [], and {} and return False for mismatched or unclosed brackets.
is_balanced("([{}])")
is_balanced("([)]")
is_balanced("(")Keep exploring.
The official references behind this chapter, when you want a second angle or the full details.
Pause. Put the pieces together.
You’ve reached the end of Applied Python Tracks. Before moving on, use this task to check what you can do independently.
Complete one track project end to end and defend its input validation, failure handling, tests, and honest output.
Try it in your own environment and write down the result. This is a self-check; reading completion does not evaluate your code.
Review this moduleOne chapter closer.
Reading is a start. Trying it yourself makes the connection.