Editorially revised on 9 October 2026.
Technical interview practice should make your assumptions, method and checks visible. This guide provides five original exercises using small Python and SQL examples, with expected results and follow-up questions. They are educational demonstrations, not leaked employer questions or a complete syllabus for every technical role. Check the actual vacancy and interview instructions when choosing what to practise.
The examples focus on reading code, controlling state, preserving output order and explaining a database result. They complement the Python resume evidence guide and role-contribution interview answers. Solving a practice problem does not establish professional experience you have not gained.
State the input and the intended behaviour first
Before changing code, explain what inputs are accepted and what result is required. Ask whether the function may mutate its input and whether order matters. For database work, identify keys, sample rows and whether missing values are permitted. A solution can be correct under one assumption and wrong under another.
Run or reason through a small normal case, an empty case and a case likely to expose the issue. These examples use specified finite inputs so that their outputs can be checked. They do not prove performance or correctness for an unspecified production system. Language and database versions can affect other behaviour; consult the relevant official documentation rather than treating a remembered slogan as a complete rule.
Question one: why does a default list retain earlier items?
Consider this Python function in a fresh interpreter. Each print runs after the preceding call, and neither call supplies the second argument.
def add_label(value, labels=[]):
labels.append(value)
return labels
print(add_label("red"))
print(add_label("blue"))
Expected printed results: First ['red'], then ['red', 'blue']. The official Python default-argument documentation explains that defaults are evaluated once. Both calls use the same default list, which the function mutates.
If the intended behaviour is a fresh list for each call that omits the second argument, one repair is:
def add_label(value, labels=None):
if labels is None:
labels = []
labels.append(value)
return labels
Now those two calls produce separate one-item results. This repair still mutates a caller-supplied list. If mutation of supplied input is prohibited, the requirements call for a different handling of that input. Explain that boundary rather than declaring that replacing a default with None solves every state-management issue.
Follow-up: If the caller passes an existing list, what happens? Trace its identity and contents before and after the call. Do not confuse a fresh default list with a guarantee that all provided inputs are copied.
Question two: does assignment make a separate list?
Read this independent Python example:
values = [1, 2]
alias = values
alias.append(3)
print(values)
Expected printed result: [1, 2, 3]. Assignment here gives another name referring to the same list. Appending through that name changes the list seen through values. The official Python tutorial's aliasing explanation describes how multiple names can refer to one object.
For this flat list, alias = values.copy() gives a separate outer list before the append. It is a shallow copy. If the elements include nested mutable objects, copying the outer list does not independently copy every nested object. State the actual structure before choosing a copying strategy.
Follow-up: Why is result = values.append(3) not a useful way to obtain the updated list? The list documentation describes append as a mutating method that returns None. The changed list and the return value are different things. Demonstrate both before replacing the expression with another guessed method.
Question three: remove duplicates while preserving first occurrence
The input is a list of integers. Return a new list containing each distinct integer in the order it first appeared. Do not mutate the input. For [4, 1, 4, 2, 1], the required output is [4, 1, 2]; for an empty list it is an empty list.
def first_occurrences(values):
seen = set()
result = []
for value in values:
if value not in seen:
seen.add(value)
result.append(value)
return result
The loop visits the input in order and appends an item only on its first encounter. The set is used for membership checks, while the result list preserves the required order. Returning the set itself would not meet the requested list contract. This exercise specifies integers; a version accepting arbitrary unhashable elements would need another strategy or a revised input contract.
Checks: Confirm the ordinary example, repeated identical values and empty input. Compare the input with its original contents after the call. If discussing complexity, identify the assumptions about set operations rather than making an unconditional worst-case linear-time claim. Explain the additional state used by both seen and result.
Question four: count child rows after a left join
Assume customers(id) has unique non-null IDs 1 and 2. tickets(id, customer_id) has unique non-null ticket IDs and contains (10, 1) and (11, 1). There are no other rows. Return every customer and the number of their matching tickets, ordered by customer ID.
SELECT c.id, COUNT(t.id) AS tickets
FROM customers AS c
LEFT JOIN tickets AS t
ON t.customer_id = c.id
GROUP BY c.id
ORDER BY c.id;
Expected rows: Customer 1 has 2 tickets; customer 2 has 0. The PostgreSQL table-expression documentation describes the null-extended row produced for an unmatched left-side row. The aggregate documentation distinguishes counting rows from counting non-null expression values.
For customer 2, COUNT(t.id) counts zero non-null ticket IDs. Replacing it with COUNT(*) would count the unmatched joined row and produce 1. The query's correctness depends on the stated non-null ticket key and the desired count of matching tickets.
Follow-up: What happens if another one-to-many table is joined before counting? Matching pairs can multiply rows. Investigate the schema and intended count before adding another join or blindly inserting DISTINCT. Also check whether a later WHERE condition on the right-side table removes the unmatched customers you intended to retain.
Question five: why does the loop return too early?
The intended contract is to sum all integers in a supplied list and return zero for an empty list. Inspect this function:
def total(values):
amount = 0
for value in values:
amount += value
return amount
Observed behaviour: For [2, 5, 7], it returns 2 because return is inside the loop. For an empty list, execution reaches the end without a return value, giving None. A repair moves the return after the loop:
def total(values):
amount = 0
for value in values:
amount += value
return amount
The corrected results are 14 for [2, 5, 7] and 0 for []. Check a single item and negative integers as additional specified-input cases. This exercise concerns return placement, not a recommendation to replace every sum operation with a custom loop.
Follow-up: Explain how indentation changes the control flow. If the required task were “return the first running total that exceeds a threshold,” an early return might be intentional. Diagnose behaviour against the actual contract, not a rule that every return inside a loop is wrong.
Keep a practice review record
After an exercise, record the input contract, your first assumption, the observed result and the check that revealed the issue. Then solve a fresh variation without copying the previous answer. For example, vary the list contents while keeping the same contract, or add a customer with no tickets to the SQL example.
| Review field | What to explain |
|---|---|
| Contract | Accepted inputs, output and mutation requirements |
| Method | Why the steps satisfy that contract |
| Check | A finite case that exposes a likely error |
| Boundary | What the solution does not handle or establish |
| Next variation | A changed case that tests understanding |
Must I use these exact languages in an interview?
No. Follow the employer's actual instructions and practise the technologies relevant to the role. These examples demonstrate reasoning under explicit conditions.
Should I memorise the answers?
Learn to explain the state, contract and checks. A changed requirement can make a memorised answer incorrect.
Do five solved exercises establish readiness?
They cover limited tasks. Compare your preparation with the actual role and investigate gaps rather than treating this lesson as a complete certification of technical capability.
