Loading...
Loading...
Python interview guide
Lists, tuples, sets, dictionaries and the collections module. Every answer is short enough to say aloud, and every question links to the lesson that teaches it.
37 questionsLast updated
It generates __lt__, __le__, __gt__ and __ge__. Each compares the tuple of the fields in declaration order, and only works when both objects are the same class. Fields marked field(compare=False) are left out. Because __eq__ is generated by default too, ordering and equality stay consistent.
Yes. min does one pass in O(n) time and O(1) extra memory. sorted costs O(n log n) time and builds a copy. Use sorting only when you need the whole order, or use heapq.nsmallest when you need the few smallest values.
Lesson: Sorting Your Own Objects: __lt__, order=True, min, max and bisectThe list must already be sorted, or answers are meaningless. Searching is O(log n), but insort still costs O(n) because later items shift along. Since Python 3.10 bisect also accepts key=. For many inserts, a heap or a tree-like structure is a better fit.
Lesson: Sorting Your Own Objects: __lt__, order=True, min, max and bisectUse __lt__ or order=True when the class has one obvious natural order, such as releases by version. Use key functions when there are several sensible orders, such as employees by pay, by name or by department. Keys keep the class free of one hard-coded rule.
Lesson: Sorting Your Own Objects: __lt__, order=True, min, max and bisectUse a tuple key with a negative number: sorted(rows, key=lambda r: (r["dept"], -r["pay"])). If both fields were text and one were descending, do two passes from the least important field to the most important, relying on stable sorting.
Each pass leaves items with equal keys in the order the previous pass produced. Sorting by pay, then by department, therefore gives department order with pay order kept inside each department. Python guarantees stability, so this is safe.
Lesson: Sorting and Comparing Checkpointmin(items). It makes one pass in O(n) and uses constant extra memory. sorted costs O(n log n) and a copy, and is only worth it when the full order is needed.
Lesson: Sorting and Comparing CheckpointFor a small list, use bisect.insort to insert each score, which is O(log n) to find the place and O(n) to shift. For very large streams where you only need the top few, a heap from heapq is cheaper, which the next module covers.
Lesson: Sorting and Comparing CheckpointDefine __lt__ or use dataclass order=True when the class has one natural order that everyone expects. Pass key when you need many different orders, since that keeps the class unchanged and each report free to sort its own way.
Lesson: Sorting and Comparing CheckpointIn CPython, append(), appendleft(), pop(), and popleft() on a deque are atomic operations implemented in C and protected by the Global Interpreter Lock (GIL). You can safely use a deque as a simple multi-threaded producer-consumer FIFO queue without manual locks.
Maintain window = deque(maxlen=K). For each incoming number, window.append(num) (which automatically drops the oldest number once full), and compute the average as sum(window) / len(window). To achieve strict O(1) average time, keep a running sum: current_sum += num - oldest_num.
A pure linked list creates an individual node object for every single element, each requiring pointer overhead (prev, next, val, PyObject header = ~40-56 bytes per element). collections.deque bundles 64 elements per block, amortizing pointer overhead across 64 items and delivering high CPU cache locality.
queue.Queue is built for threads: it blocks get() until an item arrives, supports a maximum size with blocking put(), and tracks completed tasks. A deque is faster and simpler for single-threaded queues, BFS and windows, but it never blocks.
A deque is a chain of blocks, so d[i] walks from the nearer end and costs O(min(i, N - i)). If you need many random reads, keep a list. If you only touch the ends, a deque is the right choice.
Keep indexes in a deque so their values decrease from front to back. A new value removes every smaller value from the back, because those can never be a maximum again. The front is dropped when it leaves the window. Each index enters and leaves once, so the total is O(N).
Lesson: Double-Ended Queues with collections.dequelist.append plus list.pop(0) ever acceptable as a queue?For a few dozen items yes, because the shifting is cheap. Once the queue can grow large, each pop(0) costs O(N) and the loop becomes O(N^2). deque.popleft() stays O(1), so use it by default for queues.
defaultdict(int) requires manual loops to increment (d[k] += 1). Counter automatically initializes counts directly from any iterable, provides most_common(), supports multiset arithmetic (+, -, &, |), and provides .total() (Python 3.10+).
counter.total() computes the sum of all frequency counts in O(N) time, like sum(counter.values()). It needs Python 3.10 or newer.
Counter(nums).most_common(1)[0][0] finds the top element in O(N) time. In technical rounds, interviewers often follow up by asking for the Boyer-Moore Voting Algorithm to achieve O(N) time with O(1) extra space.
When you need the grouped items themselves, not just how many there are: use defaultdict(list). Also when keys are unhashable, or when you need a stream of the top k with bounded memory: use a size-k heap.
A Counter is a dict, so it keeps insertion order. most_common() sorts by count and the sort is stable, so tied items stay in the order they were first counted. If your output must be deterministic across inputs, sort explicitly with key=lambda kv: (-kv[1], kv[0]).
Sorting costs O(N log N) and Counter costs O(N) on average, because each count update is a hash lookup. Sorting still wins when you need the data in order anyway or when keys are unhashable but comparable.
Lesson: Counting Frequencies with collections.Counterc - d drop negative counts, and how do you keep them?The operators model multisets, where a negative count has no meaning, so +, -, & and | keep only positive results. To keep negatives, call c.subtract(d), which edits c in place and keeps zero and negative counts.
dict.setdefault(key, []) always evaluates its default expression on every call, creating a new temporary list in memory even when the key already exists. defaultdict only invokes the default_factory callable when a key is genuinely missing, saving allocations in high-volume operations.
Accessing d[key] on a defaultdict inserts the key into the dictionary with its default value. If code simply queries if d["missing_key"]: or logs the value, it accidentally mutates the dictionary by creating new entries. Use "key" in d to check presence safely.
You can create an auto-vivifying tree using a recursive lambda: ``python def tree(): return defaultdict(tree) users = tree() users["org"]["dept"]["team"] = "DevOps" `` Every accessed level automatically generates another defaultdict.
dict.fromkeys(keys, []) cause a bug that defaultdict(list) avoids?fromkeys uses the same list object as the value for every key, so appending to one key changes all of them. defaultdict(list) calls list() for each missing key, so every key gets its own list.
d.get(key) use the default factory?No. The factory runs inside __missing__, which only d[key] calls. So get() returns None (or your default) and does not insert, while indexing inserts. Mixing the two styles is a common source of surprises.
Use Counter for counts, defaultdict(list or set) for grouping many records in a loop, and setdefault or get for a one-off lookup on a plain dict. A defaultdict is a good build-time tool, but return plain dicts from public functions.
Set d.default_factory = None or convert with dict(d). Either way a missing key raises KeyError, which exposes typos in reports instead of silently adding empty entries.
Because most nodes in a binary tree reside near the bottom (leaves). In a tree of height h, N/2 nodes are at the bottom and take 0 sift-down operations, N/4 nodes take 1 operation, and only the single root node takes h operations. Adding these costs gives a total proportional to N, so heapify is O(N).
Lesson: Priority Queues and Min-Heaps with heapqheapq.heappushpop(heap, item) combines both operations into a single sift operation: it compares item directly against heap[0]. If item is smaller, it immediately returns item without touching the heap. If larger, it replaces heap[0] and runs a single downward sift in O(log N), avoiding the double restructuring of separate push and pop.
If two items have the same priority and the item object does not implement __lt__ (or is a dictionary), Python raises a TypeError: '<' not supported. To avoid this, include a tiebreaker sequence counter in the tuple: (priority, count, task) where count is an incrementing integer. Since counts are unique, Python never compares the third task element.
Push a new (distance, node) entry whenever you find a shorter path. When you pop an entry whose distance is larger than the best known distance for that node, skip it. This is called lazy deletion and keeps each step O(log N).
Lesson: Priority Queues and Min-Heaps with heapqnlargest(k, data) is best when k is small compared to N. sorted(data)[-k:] is simpler when k is close to N. A hand-managed size-k heap is best for streams, because it uses O(k) memory and handles items one at a time.
Inserting into a sorted list with bisect.insort costs O(N) because items shift, while heappush costs O(log N). If you only ever need the minimum, a heap is cheaper. Use a sorted structure when you also need to read items in full order often.
For numbers, negate the value. For other types, push (-priority, item) using a numeric priority, or wrap the item in a class that defines __lt__ to reverse the comparison. Strings cannot be negated, so a wrapper class or a numeric priority is needed.
Each lesson has quizzes, practice and more interview questions on the same topic.
Open the first lesson