Python Coding Interview Questions & Answers (2026)
These interviews test core Python syntax, algorithmic thinking, and problem‑solving efficiency. Mastering built‑in data structures, time‑complexity analysis, and clean code style helps you demonstrate depth. Focus on writing correct, readable code quickly, explaining trade‑offs, and showing awareness of edge cases to impress interviewers.
21 questions · updated Aug 29, 2026
Quick facts
| Typical rounds | Phone screen, live coding, system design, and final onsite |
| Common focus | Algorithms, data structures, Pythonic idioms, and complexity |
| Preferred tools | Whiteboard or shared editor (e.g., CoderPad, HackerRank) |
| Success metric | Correctness, clarity, and ability to discuss trade‑offs |
Questions
Beginner
Write a function to reverse a string.
Use slicing for a concise, O(n) solution: def reverse_string(s): return s[::-1]. This leverages Python's built‑in sequence handling, avoids explicit loops, and clearly conveys intent. Explain that slicing creates a new string, so memory usage is O(n), which is acceptable for interview constraints.
def reverse_string(s):
return s[::-1]Find the first non‑repeating character in a string.
Traverse the string twice: first build an OrderedDict of character counts, then iterate to return the first key with count 1. This runs in O(n) time and O(1) extra space because the alphabet size is bounded. Mention that using collections.Counter also works but loses order, so OrderedDict is preferred.
from collections import OrderedDict
def first_unique(s):
counts = OrderedDict()
for ch in s:
counts[ch] = counts.get(ch, 0) + 1
for ch, cnt in counts.items():
if cnt == 1:
return ch
return NoneCheck if a list contains duplicates.
Convert the list to a set and compare lengths: return len(lst) != len(set(lst)). This is O(n) time, O(n) space, and instantly shows Pythonic thinking. Discuss edge cases like unhashable elements, where a manual loop with a seen set would be required.
def has_duplicates(lst):
return len(lst) != len(set(lst))Implement FizzBuzz for numbers 1 to n.
Iterate from 1 to n, appending 'Fizz' if divisible by 3, 'Buzz' if divisible by 5, and 'FizzBuzz' if both. Use a list comprehension for brevity: ["FizzBuzz" if i%15==0 else "Fizz" if i%3==0 else "Buzz" if i%5==0 else str(i) for i in range(1,n+1)]. This demonstrates control flow and string handling.
def fizzbuzz(n):
return ["FizzBuzz" if i%15==0 else "Fizz" if i%3==0 else "Buzz" if i%5==0 else str(i) for i in range(1,n+1)]Merge two sorted lists into a new sorted list.
Use two pointers to compare elements and build the result list in O(m+n) time, O(m+n) space. This shows understanding of linear merging without relying on built‑in sort, which would be O((m+n)log(m+n)). Explain that the algorithm preserves stability and works for any comparable type.
def merge_sorted(a, b):
i=j=0
res=[]
while i<len(a) and j<len(b):
if a[i]<=b[j]:
res.append(a[i]); i+=1
else:
res.append(b[j]); j+=1
res.extend(a[i:]); res.extend(b[j:])
return resGiven two strings, determine if they are anagrams of each other.
Count characters using collections.Counter and compare the two dictionaries. This is O(n) time and O(1) space for ASCII, O(k) for Unicode where k is distinct characters. Explain that sorting both strings also works but is O(n log n), making counting the preferred interview solution.
from collections import Counter
def is_anagram(s1,s2):
return Counter(s1)==Counter(s2)Intermediate
Find the longest substring without repeating characters.
Apply the sliding window technique with a dict storing the last index of each character. Expand the right pointer, shrink the left when a repeat appears, and track max length. This runs in O(n) time, O(min(n, alphabet)) space. Emphasize why moving the left pointer directly to last occurrence +1 avoids nested loops.
def longest_unique_substring(s):
last = {}
start = max_len = 0
for i,ch in enumerate(s):
if ch in last and last[ch] >= start:
start = last[ch] + 1
last[ch] = i
max_len = max(max_len, i - start + 1)
return max_lenImplement a binary search on a sorted list.
Iteratively maintain low and high indices, compute mid, compare target, and adjust bounds. Return the index or -1. This yields O(log n) time, O(1) space. Mention edge cases like empty list and duplicate values, and why an iterative version is preferred in interviews for clarity.
def binary_search(arr, target):
lo,hi=0,len(arr)-1
while lo<=hi:
mid=(lo+hi)//2
if arr[mid]==target:
return mid
elif arr[mid]<target:
lo=mid+1
else:
hi=mid-1
return -1Given a list of intervals, merge overlapping ones.
Sort intervals by start, then iterate merging when current.start <= last.end. This is O(n log n) due to sorting, O(n) space for the result. Explain that sorting simplifies overlap detection and that the algorithm preserves original interval order after merging.
def merge_intervals(intervals):
intervals.sort(key=lambda x: x[0])
merged=[]
for start,end in intervals:
if not merged or start>merged[-1][1]:
merged.append([start,end])
else:
merged[-1][1]=max(merged[-1][1],end)
return mergedFind the kth largest element in an unsorted list.
Use heapq.nlargest(k, nums)[-1] for brevity, which runs in O(n log k) time and O(k) space. Alternatively, implement Quickselect for average O(n) time, O(1) space. Discuss trade‑offs: heap is simpler and deterministic, Quickselect is faster on large data but more complex to explain.
import heapq
def kth_largest(nums,k):
return heapq.nlargest(k, nums)[-1]Detect a cycle in a directed graph.
Perform DFS with three-state marking (unvisited, visiting, visited). If you encounter a node in the visiting state, a cycle exists. This runs in O(V+E) time, O(V) space. Explain why recursion stack or explicit stack works, and how this approach differs from detecting cycles in undirected graphs.
def has_cycle(graph):
UNVISITED, VISITING, VISITED = 0,1,2
state={node:UNVISITED for node in graph}
def dfs(v):
state[v]=VISITING
for nb in graph[v]:
if state[nb]==VISITING:
return True
if state[nb]==UNVISITED and dfs(nb):
return True
state[v]=VISITED
return False
return any(dfs(v) for v in graph if state[v]==UNVISITED)Write a function to flatten a nested list of arbitrary depth.
Use recursion: iterate over items, if an item is a list, extend the result with a recursive call; otherwise, append the item. This runs in O(n) time where n is total elements, and uses O(d) stack space where d is maximum depth. Mention that isinstance(item, list) guards against non‑list iterables.
def flatten(lst):
res=[]
for i in lst:
if isinstance(i,list):
res.extend(flatten(i))
else:
res.append(i)
return resImplement a function to compute the power of a number using exponentiation by squaring.
Recursively or iteratively halve the exponent: if exponent is even, compute half power and square; if odd, multiply by base. This reduces time to O(log n) multiplications versus O(n) for naive multiplication. Show handling of negative exponents by returning 1/result.
def power(x,n):
if n==0:
return 1
if n<0:
return 1/power(x,-n)
half=power(x,n//2)
return half*half if n%2==0 else half*half*xAdvanced
Implement a LRU cache with get and put operations.
Combine an OrderedDict with capacity tracking: get moves the key to the end and returns value; put inserts or updates, moves to end, and evicts the oldest when over capacity. This yields O(1) average time for both operations. Explain why OrderedDict maintains order and how eviction logic preserves LRU semantics.
from collections import OrderedDict
class LRUCache:
def __init__(self, cap):
self.cap=cap
self.cache=OrderedDict()
def get(self,key):
if key not in self.cache:
return -1
self.cache.move_to_end(key)
return self.cache[key]
def put(self,key,val):
if key in self.cache:
self.cache.move_to_end(key)
self.cache[key]=val
if len(self.cache)>self.cap:
self.cache.popitem(last=False)Explain the difference between deep and shallow copy with an example.
A shallow copy replicates the outer container but references the same inner objects; modifications to nested mutable objects affect both copies. Use copy.copy for shallow and copy.deepcopy for deep, which recursively copies all nested structures. Demonstrate with a list of lists, showing that appending to an inner list after a shallow copy reflects in both, while a deep copy remains unchanged.
import copy
orig=[[1,2],[3,4]]
shallow=copy.copy(orig)
deep=copy.deepcopy(orig)
orig[0].append(99)
# shallow[0] also shows 99, deep[0] does notDesign a function to generate all permutations of a list.
Use backtracking: for each position, swap the current index with each later index, recurse, then backtrack by swapping back. This yields O(n!) time and O(n) recursion stack. Explain why swapping in‑place avoids extra memory and how the algorithm systematically explores the permutation tree.
def permute(arr, l=0):
if l==len(arr)-1:
print(arr)
else:
for i in range(l,len(arr)):
arr[l],arr[i]=arr[i],arr[l]
permute(arr,l+1)
arr[l],arr[i]=arr[i],arr[l]Implement a thread‑safe singleton in Python.
Use a metaclass with a class‑level lock. In __call__, acquire the lock, check if an instance exists, create if not, then release. This ensures only one instance across threads, O(1) access after initialization. Discuss the GIL and why explicit locks still matter for multi‑process scenarios.
import threading
class SingletonMeta(type):
_instance=None
_lock=threading.Lock()
def __call__(cls,*args,**kwargs):
with cls._lock:
if cls._instance is None:
cls._instance=super().__call__(*args,**kwargs)
return cls._instance
class MyClass(metaclass=SingletonMeta):
passExplain how Python's garbage collector works with reference cycles.
CPython uses reference counting for immediate reclamation; when a cycle of objects references each other, counts never drop to zero. The cyclic GC runs periodically, identifies unreachable cycles via generation tracking, and frees them. Mention that objects with __del__ may be left uncollected, and that weakref can break cycles deliberately.
Write a function to compute the median of a streaming integer sequence.
Maintain two heaps: a max‑heap for the lower half and a min‑heap for the upper half. Balance sizes so their difference is ≤1. Median is top of max‑heap when odd, else average of both tops. This provides O(log n) insertion and O(1) median retrieval, suitable for real‑time data streams.
import heapq
class MedianFinder:
def __init__(self):
self.low=[] # max-heap (store negatives)
self.high=[] # min-heap
def add_num(self,num):
heapq.heappush(self.low,-num)
if self.low and self.high and (-self.low[0])>self.high[0]:
heapq.heappush(self.high,-heapq.heappop(self.low))
if len(self.low)>len(self.high)+1:
heapq.heappush(self.high,-heapq.heappop(self.low))
if len(self.high)>len(self.low):
heapq.heappush(self.low,-heapq.heappop(self.high))
def median(self):
if len(self.low)>len(self.high):
return -self.low[0]
return (-self.low[0]+self.high[0])/2Implement a decorator that caches function results with a max size.
Create a wrapper that stores arguments as keys in an OrderedDict; on each call, move the key to the end. If size exceeds limit, pop the oldest entry. This mimics functools.lru_cache but shows understanding of closures, hashing of arguments, and eviction policy.
from collections import OrderedDict
def memoize(maxsize=128):
def decorator(fn):
cache=OrderedDict()
def wrapper(*args):
if args in cache:
cache.move_to_end(args)
return cache[args]
result=fn(*args)
cache[args]=result
if len(cache)>maxsize:
cache.popitem(last=False)
return result
return wrapper
return decoratorExplain the difference between @staticmethod and @classmethod with use cases.
@staticmethod does not receive any implicit first argument; it's a plain function placed in a class namespace, useful for utility helpers. @classmethod receives the class itself (cls) as the first argument, allowing factory methods or modifications of class state. Demonstrate both with examples, highlighting when inheritance affects behavior.
Common mistakes
- Using .sort() and then re‑sorting inside loops, increasing time complexity unnecessarily.
- Returning None instead of a sentinel value, causing ambiguous results in edge cases.
- Neglecting to discuss space complexity, which interviewers often probe after a solution.
- Writing overly clever one‑liners without explaining the underlying algorithmic idea.
Study plan
- Review core Python data structures and their time/space complexities.
- Practice 5–7 beginner problems daily, focusing on clean syntax and edge cases.
- Solve intermediate questions with emphasis on algorithmic patterns like sliding window and two‑pointer.
- Implement advanced topics (LRU cache, concurrency, heap‑based streams) and explain trade‑offs.
- Mock interview with timed coding sessions and post‑mortem analysis of mistakes.
FAQ
How much time should I allocate to each coding question in an interview?
Aim for 5–7 minutes for easy problems, 10–12 minutes for medium, and up to 15 minutes for hard. Spend the first minute clarifying requirements, then code efficiently, leaving a couple of minutes for testing and explanation.
Should I use built‑in functions like sorted() or implement my own algorithm?
Use built‑ins when they directly solve the problem and you can discuss their complexity. For algorithmic questions, interviewers expect you to implement the core logic (e.g., merge sort) to demonstrate understanding.
What is the best way to handle edge cases in live coding?
State edge cases before coding, incorporate them into your test plan, and verify them after implementation. Mention cases like empty inputs, single‑element lists, and large numbers to show thoroughness.
How important is Pythonic style compared to correctness?
Correctness is primary, but Pythonic style (list comprehensions, idiomatic naming) signals fluency and readability. Mention that you balance both by writing clear code first, then refactoring for idiomatic expression.
Can I ask the interviewer to run my code on a whiteboard?
Yes, request a dry‑run with sample inputs. It demonstrates confidence and helps catch logical errors before execution. Interviewers appreciate proactive validation.
Related
Ready for your next interview?
Download MiPrep AI. Load your resume and the job description. Show up ready.
Free tier · No credit card · macOS 14+ · Windows 10+
Free tier · No credit card · Runs on your Mac or Windows machine