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.
38 questionsLast updated
A shallow copy (list.copy() or l[:]) creates a new container list holding references to the exact same elements as the original. If elements are mutable (e.g. inner lists), mutating an inner list through the copy mutates it in the original. A deep copy (copy.deepcopy()) recursively traverses and clones every nested object, ensuring complete independence.
Python intentionally omits prepend(x) to discourage developers from using lists as FIFO queues, where repeated prepends would incur devastating O(N^2) cumulative overhead from element shifting. The standard library provides collections.deque with O(1) appendleft() instead.
CPython will shrink the allocated capacity if the list length drops below half of the allocated space (size < allocated / 2), preventing memory from staying permanently bloated after large temporary lists are cleared.
x in my_list O(N) and what would you use instead?A list has no index of its values, so Python compares x with items one by one until it finds a match or reaches the end. When you test membership often, build a set once (O(N)) and each lookup then costs O(1) on average. Keep the list only when order or duplicates matter.
Lesson: List Memory Growth, Slicing and Complexitygrid = [[0] * 3] * 3.The outer * 3 repeats the reference to one inner list three times, so all three rows are the same object and grid[0][0] = 1 shows up in every row. Build independent rows with [[0] * 3 for _ in range(3)], which runs the inner expression once per row.
Each remove or del in the middle shifts the tail, so deleting K items one at a time costs about K times N. Rebuild once with a comprehension such as [x for x in items if keep(x)], which is a single O(N) pass, or assign it back with items[:] = ... to keep the same list object.
Yes! A tuple’s immutability means its array of pointers cannot be changed (no adding, removing, or reassigning pointers). However, if one of those pointers points to a mutable object (like a list: t = (1, [2, 3])), running t[1].append(4) mutates the underlying list in place. The tuple still points to the same object ID.
CPython maintains a freelist array for small tuples of lengths 1 to 20. When a small tuple is deallocated, its memory block is placed on the freelist rather than released back to the OS allocator. Future allocations of equal size reuse this block without invoking malloc(), speeding up tuple generation.
collections.namedtuple is a factory function returning a class. typing.NamedTuple allows class definition syntax with explicit type annotations (class Point(NamedTuple): x: int; y: int), making it cleaner to integrate with static type linters while producing the same underlying tuple subclass.
Use a tuple for a fixed-shape record whose positions have meaning, such as (latitude, longitude) or (product, region), and when you need a hashable key. Use a list for a collection of similar items that grows, shrinks or is sorted in place. The choice also tells readers whether the data is meant to change.
Lesson: Tuples as Immutable Records and Hashable Keysa, b = b, a work without a temporary variable?Python evaluates the right side first and builds the pair (b, a). It then unpacks that pair into a and b from left to right. CPython often optimises two- and three-name swaps into stack rotations, so no tuple object is actually created, but the meaning is the same.
Lesson: Tuples as Immutable Records and Hashable KeysThe dictionary stores the key in the bucket chosen by its hash. If the hash could change, later lookups would probe different buckets and fail to find the entry. That is why mutable containers such as lists are unhashable, and why a tuple holding a list is unhashable too.
Lesson: Tuples as Immutable Records and Hashable KeysWhen a key is deleted from an open-addressed hash table, you cannot simply mark the bucket as empty (NULL). Doing so would prematurely terminate probe chains for other keys that collided past that bucket, making them invisible. Instead, Python writes a special "dummy/tombstone" marker, allowing probe searches to continue while letting new insertions overwrite the dead slot.
Lesson: Hash Tables: Sets and Dictionaries Under the HoodBecause dictionary iterators track the dense entries array index. If keys are added, the table may resize or shift entries, corrupting iterator state and causing skipped or duplicate items. Python checks an internal mutation counter (ma_version) and raises a RuntimeError if modified during iteration.
Sets use almost the same hash table engine as dictionaries, but each entry stores only the hash and the key, with no value. A set does not promise any order, so never rely on the order you see when you print or loop over one.
Lesson: Hash Tables: Sets and Dictionaries Under the HoodWith well spread hashes, each key lands in or near its own bucket, so a lookup checks one or two slots. If many keys collide, the probe sequence must walk through many occupied slots. Python reduces the risk by randomising string hashes per process, so an attacker cannot easily craft keys that all collide.
Lesson: Hash Tables: Sets and Dictionaries Under the HoodIf two objects compare equal, they must have the same hash. Lookup first finds the bucket by hash, then confirms with equality, so equal objects with different hashes would land in different buckets and never be found. That is why 1, 1.0 and True act as one key.
Lesson: Hash Tables: Sets and Dictionaries Under the HoodA plain set(items) drops duplicates but loses order. Use list(dict.fromkeys(items)): dictionary keys are unique and keep insertion order, so the first occurrence of each item stays in place. It runs in O(N) time with O(N) extra space.
In early Python, merging required mutating with d1.update(d2) or writing cumbersome dict(d1, **d2) (which failed if keys were not strings). In Python 3.5, PEP 448 introduced dictionary unpacking {**d1, **d2}. Finally in Python 3.9, PEP 584 added the clean, explicit union operators d1 | d2 and d1 |= d2.
.keys() and .items() support set operations while .values() does not?Because dictionary keys are guaranteed to be unique and hashable, perfectly satisfying mathematical set requirements. Values in a dictionary can contain duplicate items and unhashable objects (e.g. lists), so .values() cannot guarantee set properties.
types.MappingProxyType(d) returns a read-only proxy view of a dictionary. If external code attempts to mutate the proxy, Python raises TypeError. However, any mutations made to the underlying dictionary d are instantly visible through the proxy.
.items() be used like a set only sometimes?A set needs hashable members. Each item is a (key, value) pair, so the view supports set operators only while every value is hashable. Calling & on .items() with a list value raises TypeError. .keys() always works because keys are hashable by definition.
{**a, **b} and a | b?Both build a new dictionary where b wins on repeated keys. The | operator needs both operands to be dictionaries, so the intent is clear and it returns the same type for dict subclasses. The unpacking form works with any mapping and can mix in extra keys inline, such as {**a, "x": 1}.
A full sort costs O(N log N). heapq.nlargest(k, d.items(), key=lambda kv: kv[1]) keeps a heap of size K and costs about O(N log K), which is better when K is small. For K close to N, a plain sort is simpler and just as fast.
sorted() accepts any iterable and returns a new list, so the original is untouched. list.sort() works only on lists, reorders them in place and returns None. Choose sorted when the input must stay as it is or is not a list. Choose sort when the list is yours to change and you want to avoid a copy.
Timsort, a hybrid of merge sort and insertion sort. It takes O(n log n) comparisons in the worst case and close to O(n) on data that is already mostly in order, such as appended log lines. It needs extra memory of up to about n/2 items for merging.
Lesson: sorted() vs .sort(), reverse and StabilityBecause you can sort in several passes. Sort employees by name, then sort the result by department. Within each department the names stay in order because the second sort never reorders ties. It also gives predictable output: rows with equal keys do not jump around between runs.
Lesson: sorted() vs .sort(), reverse and StabilityIt follows the Python convention that a method which mutates an object returns None. That makes the bug ranked = amounts.sort() visible at once, because ranked is None, instead of silently sharing one list under two names.
Python raises TypeError because it cannot compare None with an int. Fix it by cleaning the data first, or by sorting with a key that puts missing values at the end, for example key=lambda x: (x is None, x). The next lesson shows key functions.
Not when there are ties. reverse=True keeps equal items in their original order, as if the sort were stable in the reverse direction. Reversing a finished list flips the tied items too. That is why reverse=True is the correct way to rank in descending order.
Lesson: sorted() vs .sort(), reverse and StabilityA key function maps each item to a value, and Python sorts by those values. The old cmp function compared two items directly, so it was called many times per item and could not be optimised. Keys are computed once per item and compared with fast built-in comparisons. If you hold an old comparison function, functools.cmp_to_key wraps it.
When the key is only a field lookup. They are written in C, so they are slightly faster, and they say what they do. They also take several names, such as itemgetter("dept", "pay"), which returns a tuple. Use a lambda when the key needs any calculation, such as -e.pay or s.lower().
For numbers, negate the number inside a tuple key: (e.dept, -e.pay). For text, sort in passes from the least important field to the most important one, using reverse=True on the passes that need it. This works because Python's sort is stable.
Python stores the key values in a separate array and compares those. An expensive key, for example one that opens a file or parses a date, therefore runs n times, not about n log n times. It also means a key must give the same value every time for the same item.
Lesson: Key Functions, lambda, itemgetter and attrgetterMake the key put missing values last: key=lambda x: (x is None, x). The first tuple item is False for real values and True for None, so None goes to the end. Items with None never reach the second comparison, so no TypeError occurs.
It was the manual way to sort by a computed value: build pairs of (key, item), sort them and strip the keys. The key argument does exactly this inside sorted, so you almost never write it by hand today.
Lesson: Key Functions, lambda, itemgetter and attrgetterOnly __lt__, because sorted and list.sort use the < operator. If you also need >, <=, >= you can define __lt__ and __eq__ and add functools.total_ordering, which fills in the rest, or use a dataclass with order=True.
Returning NotImplemented lets Python try the reflected operation on the other object and finally raise a clean TypeError. Raising an error or comparing blindly would break comparisons with other types that know how to handle your class.
Lesson: Sorting Your Own Objects: __lt__, order=True, min, max and bisectEach lesson has quizzes, practice and more interview questions on the same topic.
Open the first lesson