Free lesson · DSA Mastery - Interview Prep, End to End
Python for DSA
Python for DSA
Python is the most popular language for technical interviews. Its expressiveness lets you focus on the algorithm rather than the syntax, and most interviewers can read it fluently. If you do not have a strong preference, Python is usually the right default choice for interview coding.
This topic covers the Python features and built-in libraries that matter for DSA problems. It is not a Python tutorial - it focuses on collections, comprehensions, and the standard-library tools that come up in interview code.
Why Python for Interviews
Interactive animation: Why Python reads so well in interviews - and the speed it gives up.
Python's advantages for interviews are concrete:
- Concise syntax. A solution that takes 25 lines in Java often takes 10 in Python. Less typing means more time for thinking.
- Powerful built-ins.
list,dict,set,tuple, andcollectionsgive you almost everything you need without writing setup code. - No type declarations needed. You spend less time satisfying the compiler and more time solving the problem.
- Readable by anyone. Interviewers from any language background can follow Python.
The trade-off: Python is slow at the byte level. An O(n) Python solution can be 10-50x slower in wall-clock time than the equivalent Java or C++. For most interview problems this does not matter - the grader cares about complexity, not wall time. But in rare problems with massive inputs or strict time limits, Python's overhead can cause time-limit-exceeded errors.
The Core Data Structures
Interactive animation: Python's toolbox: list, dict, set, tuple, deque, heapq, Counter and defaultdict.
Python's built-in types handle most interview needs.
| Need | Python Type | Notes |
|---|---|---|
| Array / dynamic array | list |
Variable size, mixed types allowed |
| Hash map | dict |
O(1) average |
| Hash set | set |
O(1) average |
| Tuple | tuple |
Immutable, hashable |
| Stack | list (use append/pop) |
Fast at the end |
| Queue / Deque | collections.deque |
Fast at both ends |
| Priority queue | heapq |
Min-heap only |
| Sorted container | sortedcontainers.SortedList |
Not built in, but standard in interviews |
| Counter | collections.Counter |
Frequency-counting subclass of dict |
| Default dict | collections.defaultdict |
Auto-initializes missing keys |
Lists
Interactive animation: [[0] * 3] * 3 makes three names for ONE inner list - write through any row and all three change.
Python's list is a dynamic array. It is the closest equivalent to Java's ArrayList but more flexible.
arr = [5, 2, 8, 1, 9, 3]
arr.append(10) # add to end
arr.pop() # remove and return last
arr.insert(0, 0) # insert at index (O(n))
arr.remove(5) # remove first occurrence (O(n))
arr[0] # index
arr[-1] # last element
arr[1:4] # slice (returns new list)
len(arr) # size
5 in arr # membership (O(n))
arr.sort() # in-place sort
sorted(arr) # return new sorted list
A few patterns that are uniquely Pythonic:
## Reverse
arr.reverse()
reversed_arr = arr[::-1]
## Iterate with index
for i, val in enumerate(arr):
print(i, val)
## Build a list with a comprehension
squares = [x * x for x in range(10)]
## Filter with a comprehension
evens = [x for x in arr if x % 2 == 0]
## Build a 2D array (careful!)
grid = [[0] * 3 for _ in range(3)] # CORRECT
grid = [[0] * 3] * 3 # BUG: all rows share the same list
That last gotcha is one of Python's classic mistakes. [[0] * 3] * 3 creates three references to the same inner list. Modifying grid[0][0] also modifies grid[1][0] and grid[2][0]. Always use the comprehension form for nested lists.
Interactive animation: The fix: a comprehension builds a fresh inner list for every row.
Dictionaries and Sets
dict is Python's hash map.
counts = {}
counts["a"] = 1
counts["a"] = counts.get("a", 0) + 1
counts["b"] = 5
if "a" in counts:
value = counts["a"]
The get(key, default) method is Python's equivalent of Java's getOrDefault.
For sets:
seen = set()
seen.add(5)
if 5 in seen:
pass
seen.remove(5)
## Set operations
a = {1, 2, 3}
b = {2, 3, 4}
a | b # union: {1, 2, 3, 4}
a & b # intersection: {2, 3}
a - b # difference: {1}
a ^ b # symmetric difference: {1, 4}
Sets and the dictionary's keys both have O(1) average operations.
Counter
Interactive animation: Counter and defaultdict line by line - counting and grouping without key checks.
Counter is a subclass of dict specifically for counting frequencies. It is one of Python's most valuable interview tools.
from collections import Counter
## Count from an iterable
counts = Counter("aabbbc")
## Counter({'b': 3, 'a': 2, 'c': 1})
## Most common
counts.most_common(2)
## [('b', 3), ('a', 2)]
## Arithmetic
c1 = Counter("aab")
c2 = Counter("abc")
c1 + c2 # Counter({'a': 3, 'b': 2, 'c': 1})
c1 - c2 # Counter({'a': 1})
## Subtract with explicit subtract (keeps zeros and negatives)
c1.subtract(c2)
## Compare Counters
c1 == c2 # True if same elements with same counts
Counter trivializes frequency-counting problems. Many anagram and character-frequency problems collapse to one or two lines with Counter.
Defaultdict
defaultdict automatically initializes missing keys with a default value. Useful when you would otherwise need dict.setdefault or get + assign.
from collections import defaultdict
## Default to int (zero)
counts = defaultdict(int)
for c in "hello":
counts[c] += 1 # No need to check if key exists
## Default to list (empty)
groups = defaultdict(list)
for word in words:
key = sorted(word)
groups[key].append(word)
## Default to set (empty)
adj = defaultdict(set)
for u, v in edges:
adj[u].add(v)
adj[v].add(u)
The grouping pattern (defaultdict(list) plus a key extractor) is one of the most useful idioms in Python interviews. It shows up in Group Anagrams, group-by-something problems, and many graph adjacency list constructions.
Deque
Interactive animation: list.pop(0) shifts every element; a deque's popleft just moves the head.
collections.deque is a doubly-linked list with O(1) appends and pops at both ends. Use it for queues, deques, and the monotonic queue pattern.
from collections import deque
q = deque()
q.append(1) # add to right
q.appendleft(0) # add to left
q.pop() # remove from right
q.popleft() # remove from left
## For BFS:
queue = deque([start])
while queue:
node = queue.popleft()
for neighbor in adj[node]:
if neighbor not in visited:
visited.add(neighbor)
queue.append(neighbor)
Critically, list.pop(0) is O(n) because it shifts every other element. For queue operations, always use deque.popleft(), which is O(1). This is one of the most common Python performance mistakes in interview code.
Heapq
Interactive animation: heapq is a min-heap only; push negated values to get a max-heap.
Python's heap module is heapq. It implements a min-heap on top of a regular list.
import heapq
heap = []
heapq.heappush(heap, 5)
heapq.heappush(heap, 2)
heapq.heappush(heap, 8)
smallest = heapq.heappop(heap) # 2
## Heapify an existing list in O(n)
arr = [5, 2, 8, 1, 9]
heapq.heapify(arr)
## arr is now a valid min-heap
## Top k elements
heapq.nlargest(3, arr) # [9, 8, 5]
heapq.nsmallest(3, arr) # [1, 2, 5]
Python's heapq does not have a max-heap. To simulate one, negate the values:
maxheap = []
heapq.heappush(maxheap, -5)
heapq.heappush(maxheap, -2)
heapq.heappush(maxheap, -8)
largest = -heapq.heappop(maxheap) # 8
This negation trick is awkward but standard. Memorize it.
For heaps with custom keys, push tuples where the first element is the priority:
heap = []
heapq.heappush(heap, (priority, value))
priority, value = heapq.heappop(heap)
Interactive animation: heapify, nlargest, nsmallest and tuple priorities.
Common Idioms
Interactive animation: enumerate, zip, unpacking and comprehensions, line by line.
A few patterns are everywhere in Python interview code.
Enumerate
for i, val in enumerate(arr):
print(i, val)
Zip
for a, b in zip(arr1, arr2):
print(a, b)
zip stops at the shorter iterable. To get both with a fill value, use itertools.zip_longest.
Comprehensions
## List
[x * 2 for x in range(5)]
## Dict
{k: v for k, v in pairs}
## Set
{x for x in arr if x > 0}
## Generator (lazy)
sum(x * x for x in range(1000000))
Use a generator (no brackets) when you only need to iterate once, especially for memory efficiency.
Sorting
arr.sort() # in-place ascending
arr.sort(reverse=True) # in-place descending
sorted_arr = sorted(arr) # new list
arr.sort(key=lambda x: -x) # custom key
arr.sort(key=lambda p: (p[0], -p[1])) # multi-key sort
The key function transforms each element before comparison. Used for sorting by attribute, by length, by absolute value, and so on.
String Manipulation
Interactive animation: Never += in a loop: join builds the whole string in one pass.
Python strings are immutable. Operations return new strings.
s = "hello"
s.upper() # "HELLO"
s.lower() # "hello"
s.strip() # remove whitespace
s.split(",") # split by separator
",".join(parts) # join with separator
s.replace("l", "L") # replace
s.startswith("he") # check prefix
s.endswith("lo") # check suffix
s[::-1] # reverse
len(s) # length
For string building, avoid concatenation in a loop:
## Slow: O(n^2)
result = ""
for s in strings:
result += s
## Fast: O(n)
result = "".join(strings)
This is one of Python's most common interview performance mistakes.
Tuple Unpacking
Python's tuple unpacking makes some patterns much cleaner.
## Swap
a, b = b, a
## Multiple return values
def divmod(a, b):
return a // b, a % b
quotient, remainder = divmod(17, 5)
## Iterate pairs
for x, y in points:
print(x, y)
## Star unpacking
first, *rest = [1, 2, 3, 4]
## first = 1, rest = [2, 3, 4]
Working with Integers
Python integers have arbitrary precision - no overflow. This is unique among interview languages.
huge = 10 ** 100 # totally fine, just a big int
result = huge * huge # also fine
This means Python avoids one entire class of bug that bites Java and C++ solutions: integer overflow. The trade-off is that big-int arithmetic is slower than fixed-width arithmetic.
For modular arithmetic, the % operator works as expected:
result = (a * b) % MOD
Tree Node and List Node Classes
Same as Java, just with Python syntax:
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
A Note on Recursion Depth
Interactive animation: Python's recursion limit, and what to do about deep recursion.
Python's default recursion depth is 1000. Deep recursion problems can hit this limit. For interviews where you might recurse beyond 1000 levels:
import sys
sys.setrecursionlimit(10000)
Set this at the top of your solution file if you anticipate deep recursion.
Common Mistakes to Avoid
Interactive animation: Four habits to keep, and four traps to avoid.
A few Python-specific traps that show up in interview code.
list.pop(0) is O(n). Use deque.popleft() for queues.
Mutable default arguments persist across calls.
## BUG
def f(arr=[]):
arr.append(1)
return arr
f() # [1]
f() # [1, 1] not what you expected
Use None as the default and initialize inside the function.
[[0] * n] * m creates aliased rows. Use a list comprehension instead.
Dict iteration order is now guaranteed. Since Python 3.7, dicts iterate in insertion order. You can rely on this.
Comparing tuples compares element by element. (1, 5) < (1, 6) is True because the first elements tie and the second elements break the tie. Useful for sorting by multiple keys.
Boilerplate to Remember
A minimal Python interview template:
from collections import Counter, defaultdict, deque
import heapq
class Solution:
def solve(self, nums):
counts = Counter(nums)
return 0
That covers most of what you need. Python's expressiveness means you spend more time solving and less time setting up - which is exactly the advantage you want in a 45-minute interview.
The rest of this course uses Java for code examples, but the algorithmic ideas transfer directly to Python. If Python is your language of choice, read the Java code as pseudocode and translate using the patterns covered here.