Python Benchmark Test
Python Benchmark Test
Scheduled: Saturday Apr 19, 2026 Duration: 2 hours 30 min total (timed per section) Setup: No notes. No IDE autocomplete. Use plain editor or paper. Goal: Know exactly where you stand before interview prep intensifies.
Pre-Test Rules
- Start fresh. No warm-up.
- Write answers first, THEN check wiki/run code.
- Mark each answer: ✅ got it | ⚠️ partial | ❌ missed
- Honest self-score matters more than a good score.
Section 1 — Python Internals Verbal (30 min)
Write 2-3 sentence answers. No code required.
Q1. What is the GIL? When does it matter and when does it not?
Q2. What's the difference between threading and multiprocessing in Python? When do you pick each?
Q3. What does async/await do under the hood? Is it parallel? What's the event loop?
Q4. What is a context manager? Give an example of writing one with __enter__/__exit__ AND one with @contextmanager.
Q5. What happens when you run this?
def append_to(element, to=[]):
to.append(element)
return to
print(append_to(1))
print(append_to(2))
print(append_to(3))
Why? How do you fix it?
Q6. Difference between copy.copy() and copy.deepcopy()? When does shallow copy cause bugs?
Q7. What is __slots__? When would you use it?
Q8. What does @property do? Write a class with a getter and setter using it.
Q9. What is the difference between @classmethod and @staticmethod? Give one use case each.
Q10. What does functools.wraps do and why is it needed in decorators?
Section 2 — Python Coding: DSA (60 min)
Implement in Python. No imports except standard library. Time yourself per problem.
Problem 1 (15 min) — Two Sum II (sorted array)
Given a sorted array and a target, return 1-indexed positions of two numbers that sum to target. Exactly one solution exists.
def two_sum(numbers: list[int], target: int) -> list[int]:
pass
# Test cases
assert two_sum([2,7,11,15], 9) == [1,2]
assert two_sum([2,3,4], 6) == [1,3]
assert two_sum([-1,0], -1) == [1,2]
Expected: Two pointer, O(n) time, O(1) space.
Problem 2 (20 min) — Longest Substring Without Repeating Characters
def length_of_longest_substring(s: str) -> int:
pass
# Test cases
assert length_of_longest_substring("abcabcbb") == 3
assert length_of_longest_substring("bbbbb") == 1
assert length_of_longest_substring("pwwkew") == 3
assert length_of_longest_substring("") == 0
Expected: Sliding window + set, O(n) time.
Problem 3 (25 min) — Valid Parentheses + Next Greater Element
3a. Valid parentheses (10 min):
def is_valid(s: str) -> bool:
pass
assert is_valid("()") == True
assert is_valid("()[]{}") == True
assert is_valid("(]") == False
assert is_valid("([)]") == False
assert is_valid("{[]}") == True
3b. Next Greater Element (15 min): Given array, find next greater element for each position. -1 if none exists.
def next_greater_element(nums: list[int]) -> list[int]:
pass
assert next_greater_element([2,1,2,4,3]) == [4,2,4,-1,-1]
assert next_greater_element([1,3,2]) == [3,-1,-1]
Expected: Monotonic stack, O(n).
Section 3 — OOP + LLD (35 min)
No boilerplate lookup. Write from memory.
Problem 4 (35 min) — LRU Cache
Implement LRU Cache with O(1) get and put.
class LRUCache:
def __init__(self, capacity: int):
pass
def get(self, key: int) -> int:
# return value, or -1 if not found
pass
def put(self, key: int, value: int) -> None:
# insert or update. evict LRU if over capacity.
pass
# Test
cache = LRUCache(2)
cache.put(1, 1)
cache.put(2, 2)
assert cache.get(1) == 1 # returns 1
cache.put(3, 3) # evicts key 2
assert cache.get(2) == -1 # not found
assert cache.get(3) == 3
cache.put(4, 4) # evicts key 1
assert cache.get(1) == -1
assert cache.get(3) == 3
assert cache.get(4) == 4
Follow-up (verbal): How would you make this thread-safe?
Section 4 — Async + FastAPI Concepts (25 min)
Written answers only. No code required.
Q11. What's the difference between these two FastAPI routes? Which is better and why?
@app.get("/sync")
def sync_handler():
result = requests.get("https://api.example.com/data")
return result.json()
@app.get("/async")
async def async_handler():
async with httpx.AsyncClient() as client:
result = await client.get("https://api.example.com/data")
return result.json()
Q12. What does Depends() do in FastAPI? Write the signature for a dependency that validates a JWT token and returns the current user.
Q13. You have a FastAPI endpoint that sends a welcome email after user signup. The email can take 2 seconds. How do you prevent this from blocking the response? Write the endpoint signature.
Q14. What is Pydantic's model_validator vs field_validator? When do you use each?
Q15. If a FastAPI endpoint raises an unhandled exception, what HTTP status code does the client receive by default? How do you return a custom error response with 422?
Scoring Rubric
Score each answer: 2 = full | 1 = partial | 0 = missed
| Section | Max score | Your score |
|---|---|---|
| Section 1 — Internals (Q1–Q10) | 20 | |
| Section 2 — Coding (P1–P3) | 12 | |
| Section 3 — LLD (P4) | 8 | |
| Section 4 — Async/FastAPI (Q11–Q15) | 10 | |
| Total | 50 |
What your score means
| Score | Level | Action |
|---|---|---|
| 45–50 | Interview-ready | Move on. Python is not your gap. |
| 35–44 | Strong with gaps | Identify specific weak sections, 1 week polish |
| 25–34 | Moderate | 2 weeks focused on weakest 2 sections |
| Below 25 | Needs work | Spend 1 week on Python before other prep tracks |
Answer Key (open AFTER test)
Section 1 Answers
Q1 — GIL:
CPython's Global Interpreter Lock ensures only one thread executes Python bytecode at a time. Doesn't matter for I/O-bound work (threads release GIL while waiting on I/O). Kills CPU-bound parallelism — use multiprocessing instead.
Q2 — threading vs multiprocessing:
threading shares memory, cheap to create, good for I/O-bound (HTTP calls, DB queries). multiprocessing spawns separate processes with own memory, bypasses GIL, good for CPU-bound (image processing, ML inference). Use concurrent.futures as unified API.
Q3 — async/await:
Single-threaded event loop. Coroutines yield control at await points — not parallel, concurrent I/O only. Event loop runs one coroutine at a time but switches when one awaits. Don't put CPU work in async functions — it blocks the loop.
Q4 — context managers:
# Class-based
class Timer:
def __enter__(self):
self.start = time.time()
return self
def __exit__(self, *args):
print(f"Elapsed: {time.time() - self.start:.2f}s")
# Generator-based
from contextlib import contextmanager
@contextmanager
def timer():
start = time.time()
yield
print(f"Elapsed: {time.time() - start:.2f}s")
Q5 — mutable default arg:
Prints [1], [1,2], [1,2,3]. Default [] is evaluated once at function definition, shared across all calls. Fix: def append_to(element, to=None): to = to if to is not None else []
Q6 — copy vs deepcopy:
copy.copy() copies the outer object, inner objects are still shared references. copy.deepcopy() recursively copies everything. Shallow causes bugs when nested mutable objects (lists inside dicts) are modified — original changes too.
Q7 — slots:
Replaces per-instance __dict__ with fixed attribute list. Reduces memory (no dict overhead), faster attribute access. Use for data-heavy classes with millions of instances. Downside: can't add attributes dynamically.
Q8 — @property:
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
if value < 0: raise ValueError("Radius must be positive")
self._radius = value
Q9 — classmethod vs staticmethod:
@classmethod receives cls — used for alternative constructors (User.from_dict(data)). @staticmethod receives nothing — utility function that logically belongs to the class but doesn't need class/instance state (MathUtils.add(a, b)).
Q10 — functools.wraps:
Without @wraps, wrapped function loses its __name__, __doc__, __qualname__. Matters for introspection, debugging, and FastAPI which uses __name__ to generate route names.
Section 2 Answers
P1 — Two Sum II:
def two_sum(numbers, target):
l, r = 0, len(numbers) - 1
while l < r:
s = numbers[l] + numbers[r]
if s == target: return [l+1, r+1]
elif s < target: l += 1
else: r -= 1
P2 — Longest Substring:
def length_of_longest_substring(s):
seen = set()
l = res = 0
for r, c in enumerate(s):
while c in seen:
seen.remove(s[l])
l += 1
seen.add(c)
res = max(res, r - l + 1)
return res
P3a — Valid Parentheses:
def is_valid(s):
stack = []
pairs = {')':'(', ']':'[', '}':'{'}
for c in s:
if c in '([{':
stack.append(c)
elif not stack or stack[-1] != pairs[c]:
return False
else:
stack.pop()
return not stack
P3b — Next Greater Element:
def next_greater_element(nums):
res = [-1] * len(nums)
stack = [] # indices
for i, n in enumerate(nums):
while stack and nums[stack[-1]] < n:
res[stack.pop()] = n
stack.append(i)
return res
P4 — LRU Cache:
from collections import OrderedDict
class LRUCache:
def __init__(self, capacity):
self.cap = capacity
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, value):
if key in self.cache:
self.cache.move_to_end(key)
self.cache[key] = value
if len(self.cache) > self.cap:
self.cache.popitem(last=False)
Thread-safe: wrap get/put with threading.Lock().
Section 4 Answers
Q11 — sync vs async route:
Async is better. Sync route blocks a thread while waiting on HTTP. Async releases the event loop during await, allowing other requests to proceed. FastAPI runs sync routes in a threadpool — wastes threads on I/O waiting.
Q12 — Depends:
async def get_current_user(token: str = Depends(oauth2_scheme)) -> User:
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
user = await db.get_user(payload["user_id"])
if not user: raise HTTPException(401, "Invalid token")
return user
Q13 — Background task:
@app.post("/signup", status_code=201)
async def signup(user: UserIn, bg: BackgroundTasks):
created = await create_user(user)
bg.add_task(send_welcome_email, created.email)
return created
Q14 — model_validator vs field_validator:
field_validator validates a single field in isolation. model_validator runs after all fields are set — use when validation needs to cross-reference multiple fields (e.g., end_date > start_date).
Q15 — error handling: Unhandled exception → 500. Custom 422:
from fastapi import HTTPException
from fastapi.responses import JSONResponse
@app.exception_handler(RequestValidationError)
async def validation_error(req, exc):
return JSONResponse(status_code=422, content={"error": str(exc)})
Post-Test: Record Your Weak Areas
Date taken: ___________
Total score: ___ / 50
Weakest section: ___________
Specific gaps:
1.
2.
3.
Follow-up pages to re-read:
- [ ]
- [ ]