This page collects four classic routines that come up constantly in Python interviews and everyday code: intersecting two lists with sets, binary search over a sorted list, and the two divide-and-conquer sorts, quick sort and merge sort. Each has a short implementation above; the sections below add complexity analysis, traces of how the algorithms move through their input, the standard-library equivalents you should usually reach for, and the mistakes that most often break hand-written versions.
def list_intersection(list1: list, list2: list) -> list:
return list(set(list1) & set(list2))
print(list_intersection([1, 2, 3, 4], [3, 4, 5, 6])) # Output: [3, 4]
def binary_search(arr, target):
low = 0
high = len(arr) - 1
while low <= high:
mid = (low + high) // 2
if arr[mid] == target:
return mid
elif arr[mid] < target:
low = mid + 1
else:
high = mid - 1
return -1
def quick_sort(arr):
if len(arr) <= 1:
return arr
pivot = arr[0]
left = [x for x in arr[1:] if x <= pivot]
right = [x for x in arr[1:] if x > pivot]
return quick_sort(left) + [pivot] + quick_sort(right)
def merge_sort(arr):
if len(arr) <= 1:
return arr
mid = len(arr) // 2
left = merge_sort(arr[:mid])
right = merge_sort(arr[mid:])
return merge(left, right)
def merge(left, right):
result = []
while left and right:
if left[0] <= right[0]:
result.append(left.pop(0))
else:
result.append(right.pop(0))
if left:
result += left
if right:
result += right
return result
| Algorithm | Best | Average | Worst | Space |
|---|---|---|---|---|
| List intersection (sets) | O(n+m) | O(n+m) | O(n·m) | O(n+m) |
| Binary search | O(1) | O(log n) | O(log n) | O(1) |
| Quick sort | O(n log n) | O(n log n) | O(n²) | O(n) as written |
| Merge sort | O(n log n) | O(n log n) | O(n log n) | O(n) |
The worst case for set intersection only occurs with pathological hash collisions; in practice it is linear. The quick sort shown builds new lists at every level, so it uses O(n) extra memory per level of recursion on top of the O(log n) average depth; an in-place partitioning version uses only the recursion stack.
Binary search keeps an interval [low, high] that must contain the target if it exists. Comparing with the middle element discards half of that interval each step, so at most about log2(n) + 1 comparisons are needed: 20 for a million items, 30 for a billion. The precondition is that the list is sorted by the same key you search on; on unsorted input the function returns wrong answers silently rather than failing.
In real code use the bisect module, which implements the same loop in C and returns insertion points rather than just found/not-found:
from bisect import bisect_left
def index_of(a, x):
i = bisect_left(a, x)
return i if i < len(a) and a[i] == x else -1
print(index_of([2, 5, 8, 12, 16, 23, 38], 23)) # 5
Both algorithms split the problem in two, but they put the effort in different places. Quick sort does its work up front: partitioning around a pivot places every element on the correct side, after which the halves are sorted independently and simply concatenated. Merge sort splits trivially at the midpoint and does its work afterwards, merging two sorted halves by repeatedly taking the smaller head element.
This difference explains their trade-offs. Merge sort always produces balanced halves, so it is guaranteed O(n log n) and is stable, but it needs O(n) auxiliary space for merging. Quick sort can be done in place and has excellent cache behaviour, but a bad pivot (for example the first element of already-sorted input, as in the version above) produces one empty side and one side of n−1 elements, degrading to O(n²) and recursion depth n. Choosing a random pivot or the median of three makes that case very unlikely. Python's built-in sorted uses Timsort, a merge-sort hybrid that exploits existing runs in the data.
set(a) & set(b) returns an unordered result with duplicates removed. If you need the order of a, use s = set(b); [x for x in a if x in s].high = len(a) with while low <= high reads past the end. Pick one convention (closed or half-open interval) and use it throughout.RecursionError around 1000 elements.sorted, list.sort, bisect and heapq, which are faster and well tested.