DSA in Python roadmap: what to learn, in what order
11 min readUpdated
Data structures and algorithms decide most coding rounds, and Python is a good language to learn them in because its built-in tools are strong. This roadmap gives you an order that builds each topic on the last, the patterns that solve most problems, and a practice routine that works.
Before you start
You need core Python first: loops, functions, lists, strings, dictionaries and sets. If those still feel new, finish them first. DSA practice goes much faster afterwards.
The order to learn in
Each topic builds on the one before it. Move on only when you can do the check in the last column without help.
| Step | Topic | You are ready to move on when… |
|---|---|---|
| 1 | Complexity (Big-O) and how to approach a problem | You can state the time and space cost of your own code |
| 2 | Lists and strings | You solve easy list and string problems without hints |
| 3 | Hashing (dict, set, Counter) | You reach for a dict to remove a nested loop |
| 4 | Two pointers and sliding window | You spot "subarray" and "substring" problems |
| 5 | Prefix sums and binary search (also on the answer) | You write a binary search with no off-by-one bug |
| 6 | Recursion and backtracking | You can draw the recursion tree for subsets and permutations |
| 7 | Linked lists, stacks, queues, monotonic stack | You reverse a list and solve bracket problems easily |
| 8 | Trees, BST, heaps | You write DFS and BFS on a tree from memory |
| 9 | Graphs: BFS, DFS, topological sort, Dijkstra, union-find | You can model a problem as a graph |
| 10 | Greedy and intervals | You can explain why a greedy choice is safe |
| 11 | Dynamic programming (1D, then 2D) | You go from recursion to memoisation to a table |
The Python tools you should know
Python ships with the structures that other languages make you build. Knowing them saves time in a coding round.
| Need | Use | Why |
|---|---|---|
| Fast lookup, counting, grouping | dict, set, collections.Counter, collections.defaultdict | O(1) average lookup |
| Queue (BFS) or double-ended queue | collections.deque | popleft() is O(1). list.pop(0) is O(n) |
| Min-heap, top-k, scheduling | heapq | heappush and heappop are O(log n) |
| Binary search in a sorted list | bisect.bisect_left, bisect.bisect_right | Already written and tested |
| Memoisation | functools.lru_cache | Caches results of a recursive function |
| Sorting with a rule | sorted(items, key=...) | Stable, O(n log n) |
Max-heap: heapq only has a min-heap. Push the negative of each number to get a max-heap.
Patterns that solve most problems
Most interview problems are variations of a few patterns. Learn to spot the pattern from the wording of the problem.
| If the problem says… | Try |
|---|---|
| Sorted list, pair or triplet with a sum | Two pointers |
| Longest or shortest subarray or substring with a condition | Sliding window |
| Count subarrays with a given sum | Prefix sums with a dict |
| Sorted input, or "minimum value that works" | Binary search (on the index or on the answer) |
| Find all subsets, permutations or arrangements | Backtracking |
| Nearest greater or smaller element | Monotonic stack |
| Shortest path with equal edge cost | BFS |
| Shortest path with different costs | Dijkstra with heapq |
| Order of tasks with dependencies | Topological sort |
| Choices that overlap, "how many ways", "minimum cost" | Dynamic programming |
| Top k, k-th largest, running median | Heap |
Five patterns in code
Short, complete examples. Each one was run, and the output shown is real.
def two_sum(nums, target):
seen = {} # value -> index
for i, value in enumerate(nums):
need = target - value
if need in seen:
return [seen[need], i]
seen[value] = i
return []
print(two_sum([2, 7, 11, 15], 9))
print(two_sum([3, 2, 4], 6))
Output
[0, 1] [1, 2]
def longest_unique_substring(text):
last = {} # char -> last index
start = best = 0
for i, ch in enumerate(text):
if ch in last and last[ch] >= start:
start = last[ch] + 1 # move the window past the repeat
last[ch] = i
best = max(best, i - start + 1)
return best
print(longest_unique_substring("abcabcbb"))
print(longest_unique_substring("pwwkew"))
Output
3 3
from bisect import bisect_left
def search(sorted_nums, target):
i = bisect_left(sorted_nums, target)
if i < len(sorted_nums) and sorted_nums[i] == target:
return i
return -1
print(search([1, 3, 5, 7, 9], 7))
print(search([1, 3, 5, 7, 9], 4))
Output
3 -1
from collections import deque
def shortest_path(graph, start, goal):
queue = deque([(start, 0)])
seen = {start}
while queue:
node, steps = queue.popleft()
if node == goal:
return steps
for nxt in graph[node]:
if nxt not in seen:
seen.add(nxt)
queue.append((nxt, steps + 1))
return -1
graph = {"A": ["B", "C"], "B": ["D"], "C": ["D"], "D": ["E"], "E": []}
print(shortest_path(graph, "A", "E"))
Output
3
from functools import lru_cache
@lru_cache(maxsize=None)
def ways(n):
# ways to climb n stairs taking 1 or 2 steps at a time
if n <= 1:
return 1
return ways(n - 1) + ways(n - 2)
print(ways(5))
print(ways(40))
Output
8 165580141
Mistakes that cost marks in Python
list.pop(0)as a queue. It is O(n) per call. Usecollections.dequeandpopleft().- Recursion depth. Python stops at about 1000 nested calls by default. For deep recursion, write the loop with a stack, or use memoisation with care.
- *Copying a nested list with `[[0] m] * n
.** All rows are the same list. Use[[0] * m for _ in range(n)]`. - Slicing inside a loop.
nums[i:]copies the list each time, so a loop becomes O(n²). Pass indexes instead. - Mutating while looping. Do not add or remove items of a list while you loop over it.
+=on strings in a loop. Collect pieces in a list and"".join(...)once.
A practice routine that works
- Learn one topic from a lesson. Then solve the must-do problems for that topic before you move on.
- Try each problem for 20 to 30 minutes. Then read the solution, close it, and write it again from memory the next day.
- Say the brute force first, give its time cost, then improve it. Interviewers want to hear this.
- Keep a short notebook: the problem, the pattern, and the one idea that unlocked it.
- Every two weeks, redo five old problems at random. You keep skills by repeating them.
- Count progress by topics you can solve cold, not by the number of problems you have seen.
The course has a DSA sheet that lists the must-do problems by topic, so you always know what to solve next.
Frequently asked questions
Is Python fast enough for coding rounds?
Yes. Online coding rounds accept Python, and the time limits are set so a correct solution with the right complexity passes. Choose the right algorithm first. Raw speed matters less.
How long does it take to learn DSA in Python?
Most students need about three to four months of steady daily practice to cover the topics above. Pace depends on how strong your Python basics are.
Should I learn DSA before or after Python OOP?
Finish Python basics, then start DSA. You can learn OOP and DSA side by side. Most coding rounds test DSA more than OOP, so give DSA more daily time.
How many problems should I solve?
There is no magic number. Solving the must-do problems of each topic, and being able to re-solve them cold, is worth more than a large count of problems you only read.
Learn it step by step
- From Brute Force to a Better Solution
- What Big-O Means and Counting Steps
- Two Pointers from Both Ends: Pair Sum, Palindrome and Container
- Variable-Size Windows
- Binary Search on the Answer
- Preorder, Inorder and Postorder Traversals
- BFS and Shortest Path by Number of Edges
- From Recursion to Memoization to a Table
- Common Pattern and Data Structure Questions
Practise this, step by step
Short lessons with quizzes, practice and interview questions, from your first program to job-ready.