Python Interview Questions: Data Model, Dunder Methods and Protocols
Reviewed by Mark Dickie · Last updated
3 questions on this page you can answer and see marked on the spot. Go to the first one
The Python data model is the set of special methods that lets your classes take part in built-in syntax, operators, and functions. Interviewers use it to see whether you know how len(), for loops, and with blocks reach into an object, and whether you can predict what a class does when it implements only part of a protocol. Expect questions on special methods being looked up on the type rather than the instance, the __eq__ and __hash__ pairing, and the iterator protocol.
Which data model behaviours do Python interviews probe?
| Topic | What the interviewer checks | Typical wrong answer |
|---|---|---|
__len__ and __getitem__ | That len(), indexing, and in fall back to __getitem__ iteration when __iter__ is missing | Assuming in always needs __contains__ |
__eq__ and __hash__ | That defining __eq__ without __hash__ sets __hash__ to None, so instances are unhashable | Saying the object still works as a dict key |
__bool__ and __len__ | That truthiness tries __bool__ first, then __len__, and otherwise defaults to True | Treating an empty container as always truthy |
__repr__ and __str__ | That print() and str() use __str__, while repr() and the REPL use __repr__ | Thinking __str__ alone changes debugger output |
Descriptors (__get__, __set__) | That a data descriptor on the class overrides an entry in the instance __dict__ | Believing an instance attribute always wins |
__enter__ and __exit__ | That __exit__ runs even when the block raises, and that a true return value suppresses the exception | Forgetting that cleanup runs on error |
__new__ and __init__ | That __new__ creates the object and __init__ only initialises it, and that __new__ can return a different type | Treating __init__ as the constructor |
How should you practise the Python data model before the interview?
- Write a
Vectorclass with__len__and__getitem__only, then check thatlist(v),x in v, andforloops all work. - Give a class
__eq__but no__hash__, then put two instances in a set and read the TypeError. - Build a context manager with
__enter__and__exit__, raise inside the block, and confirm the cleanup still runs. - Override
__getattr__and__getattribute__on one class and log which one fires for a missing attribute and for an existing one. - Set
obj.__len__ = lambda: 99on an instance and confirmlen(obj)ignores it, since special methods are looked up on the type.
Key facts
- Tarmac has 50 Python interview questions on this topic, 10 of them on this page, at difficulty 2–4 of 5.
- Tarmac tracked 8,123 job postings asking for Python in October 2026.
- Roles asking for Python advertise a median base salary of £75,000, across 2,102 job postings as of October 2026.
- Tarmac last reviewed these Python interview questions on 9 November 2026.
At a glance
| Questions | 10 shown · 50 in the bank |
|---|---|
| Difficulty | 2–4 of 5 |
| Formats | Multiple choice, Multiple answer, True / false, Find the bug, Short answer, Flashcard, Coding exercise, Code output |
| Interactive | 2 run your code against tests, in the app |
What you'll review
- mutability identity
- truthiness
- string handling
- numeric model
- dunder methods
- dict set
- mutable default args
- collections
Practice questions
Try one before you open the answer. Pick an option and press Check; it's marked on the spot.
Given a = [1, 2] and b = [1, 2], what are a == b and a is b?#
Options
Show answer
a == b is True and a is b is False. The == operator compares values, and both lists hold the same elements, so it is True. The is operator compares identity — whether two names point at the same object in memory — and because these are two separately-constructed lists, it is False.
== compares values, and the two lists hold the same elements, so it is True. is compares identity — whether both names point at the same object in memory — and these are two separately-constructed lists, so it is False.
Which of these values are falsy (i.e. bool(value) is False)? Select all that apply.#
Options
Pick every one that applies.
Show answer
The falsy values are the empty list [], the number zero 0, and the empty string — but not the string "0" or the list [0]. Truthiness depends on emptiness, not on the contents being zero-like, so a non-empty string "0" and a non-empty list [0] are both truthy even though they contain a zero.
Empty containers ([]), the number zero (0), and the empty string ("") are all falsy. The string "0" is a non-empty string and the list [0] is a non-empty list — both are truthy, because truthiness depends on emptiness, not on the contents being zero-like.
Python strings are immutable, so s[0] = 'z' raises a TypeError.#
Options
Show answer
True. Python strings are immutable, so there is no in-place item assignment and s[0] = 'z' raises a TypeError reporting that a str object does not support item assignment. Methods like s.replace(...) return a brand-new string rather than mutating the original.
str is immutable — there is no in-place item assignment, so s[0] = 'z' raises TypeError: 'str' object does not support item assignment. Methods like s.replace(...) return a brand-new string rather than mutating the original.
average([1, 2]) should return 1.5 but returns 1. Which line is wrong?#
def average(nums):
return sum(nums) // len(nums)
print(average([1, 2])) # prints 1, want 1.5Show answer
The bug is on line 2.
// is floor division, which discards the fractional part and yields an int. Use true division / for a float average: return sum(nums) / len(nums).
What is the difference between is and == in Python?#
Show answer
== compares value via the __eq__ protocol — two distinct objects are equal if their contents match. is compares identity: whether both names point to the exact same object in memory (the same id). Two lists [1, 2] are == but not is. Use is only for singletons like None.
== is value equality (dispatches to __eq__); is is object identity (same memory location). The classic trap is using is to compare numbers or strings, which only happens to work because of interning. Reserve is for None/singletons.
Which dunder method does len(obj) call, and what must it return?#
Show answer
len(obj) calls obj.__len__(), which must return a non-negative integer. Defining it also makes the object truthy/falsy by length (an empty container is falsy) unless __bool__ is defined.
Built-ins delegate to dunder protocols: len() → __len__, iter() → __iter__, str() → __str__. __len__ must yield a non-negative int, and in the absence of __bool__ it also drives truthiness.
Name two practical differences between a list and a tuple.#
Show answer
A list is mutable (you can append/reassign elements); a tuple is immutable once created. Because tuples are immutable and hashable (when their contents are), they can be dict keys or set members and signal fixed-size, heterogeneous records — lists cannot be keys.
Mutability is the headline difference: lists support in-place edits, tuples don't. Immutability makes a tuple hashable, so it can key a dict or join a set, and conventionally signals a fixed record rather than a homogeneous collection.
Implement deep_merge(base, override): recursively merge two dicts, returning a NEW dict without mutating either input. When both have a dict at the same key, merge them recursively; otherwise the override value wins. Non-dict values (including lists) replace wholesale — no list concatenation.#
Starter code
def deep_merge(base, override):
# TODO: recursive merge, override wins, no mutation
merged = dict(base)
merged.update(override)
return mergedYour solution must pass
- nested dicts merge
- scalar overrides dict
This one is written and run, not read. Solve it in the app and your code is executed against these tests and the hidden ones.
What does this print, one value per line?#
def append_to(x, lst=[]):
lst.append(x)
return lst
print(append_to(1))
print(append_to(2))
print(append_to(3))Show answer
[1]
[1, 2]
[1, 2, 3]
The default lst=[] is created once at definition time, so every call that omits lst shares that single list. Each call appends to the accumulating list, producing [1], then [1, 2], then [1, 2, 3] — the classic mutable-default-argument bug. Use lst=None and create a fresh list inside the body to fix it.
Implement run_lru(capacity, commands): a fixed-capacity LRU cache driven by a command list. Each command is ["put", key, value] or ["get", key]. get returns the value or -1 if absent and marks the key most-recently-used; put inserts/updates and marks it most-recently-used, evicting the least-recently-used key when at capacity. Return the list of results from each get (in order). An OrderedDict (or dict insertion order with move_to_end/popitem) makes this O(1) per op.#
Starter code
def run_lru(capacity, commands):
# TODO: LRU eviction; collect each get's result
results = []
return resultsYour solution must pass
- eviction then miss
- get refreshes recency
This one is written and run, not read. Solve it in the app and your code is executed against these tests and the hidden ones.
Sources
The official documentation these questions are checked against:
Related interview questions
Job market
See python salaries and hiring demand from live job postings.
The other 40 questions
This page shows 10 and marks what you pick. That's as far as a page can go. A free account opens the other 40 and keeps every answer. What you miss comes back until it's right: after a day, then at longer gaps.
Free · the whole bank · 100 answers per 30 days, each marked with written feedback