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?

TopicWhat the interviewer checksTypical wrong answer
__len__ and __getitem__That len(), indexing, and in fall back to __getitem__ iteration when __iter__ is missingAssuming in always needs __contains__
__eq__ and __hash__That defining __eq__ without __hash__ sets __hash__ to None, so instances are unhashableSaying the object still works as a dict key
__bool__ and __len__That truthiness tries __bool__ first, then __len__, and otherwise defaults to TrueTreating 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 exceptionForgetting 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 typeTreating __init__ as the constructor

How should you practise the Python data model before the interview?

  1. Write a Vector class with __len__ and __getitem__ only, then check that list(v), x in v, and for loops all work.
  2. Give a class __eq__ but no __hash__, then put two instances in a set and read the TypeError.
  3. Build a context manager with __enter__ and __exit__, raise inside the block, and confirm the cleanup still runs.
  4. Override __getattr__ and __getattribute__ on one class and log which one fires for a missing attribute and for an existing one.
  5. Set obj.__len__ = lambda: 99 on an instance and confirm len(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

Questions10 shown · 50 in the bank
Difficulty2–4 of 5
FormatsMultiple choice, Multiple answer, True / false, Find the bug, Short answer, Flashcard, Coding exercise, Code output
Interactive2 run your code against tests, in the app

What you'll review

  1. mutability identity
  2. truthiness
  3. string handling
  4. numeric model
  5. dunder methods
  6. dict set
  7. mutable default args
  8. 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.

Why:

== 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.

Why:

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.

Why:

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.5
Show answer

The bug is on line 2.

Why:

// 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.

Why:

== 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.

Why:

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.

Why:

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 merged

Your 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]
Why:

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 results

Your 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.

Start with this topic

Free · the whole bank · 100 answers per 30 days, each marked with written feedback

Interview booked? Practise the job ad instead

What moved, monthly

One email a month when the bulletin comes out: what moved in the markets we track, and the new question topics we published. Confirm your address to join. Unsubscribe any time.