This interview guide is for educational and informational purposes only. It is designed to help readers prepare, but it does not guarantee any interview result, hiring decision, offer, or outcome. Interview questions, hiring criteria, and preferred answers can vary by employer, interviewer, industry, location, and time. The examples and explanations reflect the authors' research and judgment, are provided without warranties of any kind, and should not be treated as the only correct approach. Diagrams are simplified illustrations intended to highlight the main components and their interactions; actual systems and implementations may be more complex. Alternative approaches may be equally valid or better suited to a particular question, context, or interviewer. To the fullest extent permitted by applicable law, the author, contributors, and publisher are not liable for decisions made, actions taken, or losses incurred based on this guide.
Identity, Image, and Privacy Notice
To respect individual privacy, some names, profile photographs, avatars, biographical details, and other identifying information displayed in this guide may be replaced with pseudonyms, licensed stock images, illustrative avatars, composite images, or representative descriptions. Unless a person is expressly identified as an actual contributor, a displayed name, image, or profile should not be understood as depicting or identifying a specific candidate, interviewer, employee, or other real individual. These representations are provided for editorial and illustrative purposes only and do not imply endorsement, employment, participation, or affiliation with this guide or any company mentioned in it. Any resemblance to an actual person is coincidental.
Company Notice
This guide is an independent educational resource and is not affiliated with, endorsed by, sponsored by, or approved by the company named in this guide. Company names are used only to identify interview experiences commonly reported by candidates. Interview practices can change without notice, and inclusion of company-specific content does not mean these questions are official, complete, or guaranteed to be asked. To the fullest extent permitted by law, the author, contributors, and publisher are not responsible for outcomes related to use of this material.
Questions or comments?
Contact us for general questions, or share feedback, technical corrections, and comments with the community.
1. What is the difference between a heap variable and a variable inside a function, and how can Python thread conflicts still arise when each thread has local variables?Language SpecificEasyMicrosoft
Short Interview Answer (30-60 seconds)
The practical difference is that a function local variable is a name in one function call, while a heap variable is not a formal Python variable category. Python names refer to objects, and objects usually live in interpreter managed memory. Each thread calling a function gets a separate execution frame with separate local names. However, those names can still refer to the same list, dictionary, class instance, global object, or external resource. Threads can conflict when they change that shared state without coordination. The GIL does not make a complete read, modify, and write sequence safe, so shared updates may need a Lock.
Detailed Explanation
The practical rule is to check whether the object is shared, not whether the variable name is local. Python does not define heap variables as a separate language category. A variable is a name bound to an object. Inside a function, a local name belongs to that function call and is stored as part of its execution frame. Each thread calling the function has a separate call and separate local bindings.
Useful Questions to Ask the Interviewer
Should I focus on Python language behavior, or also explain the runtime and standard library?
Which Python version and execution environment should I assume?
Would you like a small code example together with production tradeoffs and edge cases?
However, a local name can refer to an object that another thread also references. This can happen with a global list, a shared class instance, a mutable default argument, an object passed to both threads, a file, or a database record. If both threads perform a read, modify, and write sequence on that shared state, their steps can overlap and cause a race condition.
On normal CPython builds, the GIL limits Python bytecode execution to one thread at a time. It does not make a sequence of operations atomic. Free threaded CPython builds can also run Python code in parallel. In production, prefer separate immutable data or message passing. When shared mutation is required, protect the complete critical section with a Lock and keep that section small.
Where it is used
This behavior matters in worker threads that update shared caches, counters, class instances, files, queues, logs, or database state. Local names are suitable for temporary calculations when each call creates and owns its data. When local names refer to shared mutable objects, production code should use a Lock, a synchronized queue, another documented thread safe abstraction, or a design that gives one thread ownership of the mutable state.
Why Interviewers Ask This
Interviewers ask this to check whether the candidate understands that Python variables are names bound to objects. They want the candidate to separate the scope of a local name from the ownership of the object referenced by that name. The question also tests whether the candidate can identify shared mutable state, explain race conditions, understand the limits of the GIL, and choose suitable synchronization for production code.
Common interview mistakes
A common mistake is treating a local name as proof that its referenced object is private to one thread. Another mistake is treating heap variable as an official Python scope category. Developers may also assume that the GIL prevents every race condition, but a multi step update can still be interrupted between operations. Another error is locking only the final write while leaving the related read and calculation outside the lock. Mutable default arguments are also easy to share accidentally because the same default object is reused across calls. Holding a lock during slow file, network, or database work can unnecessarily block other threads.
Interview tip
Start by saying that Python variables are names bound to objects. Then separate the local name from the possibly shared object. Explain that each function call has separate local bindings, but two bindings can reference the same mutable state. Finish by stating that the GIL is not a replacement for a Lock around a complete shared update.
Interviewer may ask next
Can two threads safely modify lists that are created locally inside separate function calls?
Yes, they normally do not conflict when each call creates a new list and that list is not shared outside the call. Each thread has a separate execution frame, and each local name refers to a different list object. This matters because there is no shared mutable state between the updates. The behavior changes if both calls receive the same list, use the same mutable default argument, read the list from a global name, or obtain it from a shared object.
Should one global Lock protect every shared object in a threaded application?
No, one global Lock can be correct, but it may reduce concurrency because unrelated operations must wait for the same lock. The exact tradeoff is simplicity versus parallel progress. Separate locks for independent state, synchronized queues, immutable messages, or single owner designs can reduce waiting. However, using several locks increases coordination complexity and can cause deadlock when threads acquire them in different orders.
2. If multiple Python threads access the same variable and do not write to it, is there any problem?Language SpecificEasyMicrosoft
Short Interview Answer (30-60 seconds)
No, multiple Python threads can normally read the same variable safely when no thread changes the variable binding, the referenced object, or relevant nested state. A lock is usually unnecessary for true read only access. I would still verify that no property, method, callback, or background task can mutate the shared data.
Detailed Explanation
No, multiple Python threads can normally read the same variable safely when no thread changes the binding, the referenced object, or relevant nested state. Reading a name obtains a reference to an object. It does not copy or modify the object, so a normal read has constant time overhead and does not allocate a copy.
Useful Questions to Ask the Interviewer
Should I focus on Python language behavior, or also explain the runtime and standard library?
Which Python version and execution environment should I assume?
Would you like a small code example together with production tradeoffs and edge cases?
For example, worker threads can read the same string, number, deeply immutable tuple, or immutable configuration snapshot. A lock is usually unnecessary for this true read only case.
The main limitation is that no assignment is not the same as no mutation. A method, property, lazy cache, iterator, shared list, shared dictionary, or nested mutable object may change state during an apparent read. If a writer exists, readers may observe different logical states. Iterating over a dictionary while its size changes can raise RuntimeError.
The Global Interpreter Lock is not the reason this design is correct. It does not make a sequence of operations atomic, and modern CPython also supports free threaded builds. ([docs.python.org](https://docs.python.org/3/howto/free-threading-python.html?utm_source=chatgpt.com))
In production, finish constructing shared data before starting workers. Prefer immutable snapshots. Use a lock or another synchronization method when updates are possible or several reads must form one consistent snapshot.
Where it is used
This behavior is used when worker threads share fixed configuration, constant lookup values, immutable rules, prepared reference data, or a snapshot that is published once and never changed. Reading the reference does not copy the complete object, so the read normally has constant time overhead and no memory cost for duplicating the shared value.
Why Interviewers Ask This
Interviewers ask this to test whether the candidate can distinguish safe concurrent reads from unsafe shared mutation. They also want to see whether the candidate understands Python object references, mutable state, synchronization, and why the Global Interpreter Lock alone is not a complete thread safety argument.
Common interview mistakes
A common mistake is assuming that no visible assignment means no mutation. A property, method, iterator, lazy cache, or nested collection may still change state. Another mistake is claiming that the Global Interpreter Lock makes every shared operation safe. It does not make several related operations one atomic action or guarantee one consistent snapshot. Developers may also overlook a background task that updates data which appears read only to the current function.
Interview tip
Start with the direct conclusion that true concurrent reads are normally safe and do not need a lock. Then state the condition clearly: the variable binding, the object, and relevant nested state must remain unchanged. Finish by explaining that the Global Interpreter Lock is not the reason for correctness.
Interviewer may ask next
What changes if several threads read a dictionary while another thread updates it?
That is no longer true read only access. The writer mutates the shared dictionary while readers use it, so readers can observe different logical states, and iteration can raise RuntimeError when the dictionary size changes. This matters because a successful individual operation does not make a larger sequence of reads consistent. Use a lock around related access or publish an immutable snapshot. The tradeoff is synchronization or copying cost in exchange for predictable data.
Should every shared read use a lock as a precaution?
No, a lock is usually unnecessary when the shared data is completely built before the threads start and remains truly read only. Adding a lock creates synchronization work, possible contention, and extra code complexity. A lock is appropriate when writers exist, when several reads must observe one consistent snapshot, or when an apparent read may mutate internal state. The tradeoff is stronger coordination guarantees versus lower concurrency and added implementation cost.
3. How would you design a Python data-access layer whose source can change at runtime?Language SpecificMediumMicrosoft
i Question Details
Design retrieval and update operations where the active source can be a database, an API, or a file and may change at runtime. Keep the implementation loosely coupled and explain how Dependency Injection, Factory, or Strategy patterns would be applied.
Short Interview Answer (30-60 seconds)
I would define one small DataSource contract with retrieve and update methods. Database, API, and file classes would implement that contract. I would inject the active source into a DataAccessService, use a factory only to build sources from trusted configuration, and replace the injected Strategy when the source changes. Each operation would first capture the current source, so a concurrent switch cannot split one operation across two sources. Business code stays independent from storage details and tests can inject a fake source.
I would define one DataSource Protocol with retrieve and update methods. In Python, Protocol supports structural typing. This means a class satisfies the contract by providing the required methods, even without inheriting from a common base class.
Useful Questions to Ask the Interviewer
Should I focus on Python language behavior, or also explain the runtime and standard library?
Which Python version and execution environment should I assume?
Would you like a small code example together with production tradeoffs and edge cases?
DatabaseSource, ApiSource, and FileSource implement the same contract. DataAccessService receives one source through constructor injection. It delegates each call to that object. The object is also the Strategy because it decides how the operation is performed. A factory converts trusted configuration into the correct concrete source.
At runtime, set_source replaces the active object. Each retrieve or update first captures the current source while holding a small lock, then releases the lock before doing input or output. Therefore, an operation that already started keeps using one source, while later operations can use the new source.
The extra dispatch and lock work are constant time and use constant additional memory. The real cost depends on the source. The sample file implementation reads and writes the complete JSON object, so an update takes linear time and memory in the file size. In production, I would use atomic file replacement, timeouts for APIs, transactions for databases, and cleanup of an old source until active operations finish.
Example
The code defines a DataSource Protocol with retrieve and update methods. DatabaseSource and ApiSource use dictionaries to simulate their storage behavior. FileSource persists one JSON object and uses a lock plus atomic file replacement for safer updates inside one process. DataSourceFactory creates a source from trusted configuration. DataAccessService receives the source through dependency injection. Its set_source method swaps the Strategy while holding a lock. Each operation captures the current source under the same lock and then calls it after releasing the lock. This keeps one operation on one source without holding the service lock during database, network, or file input and output. The service adds constant time dispatch and constant additional memory. File updates remain linear in the JSON file size because the complete object is copied, changed, and written.
Code
from __future__ import annotations
import json
import os
import threading
from pathlib import Path
from typing importAny, Protocol
classDataSource(Protocol):
"""Contract that every source must provide."""defretrieve(self, key: str) -> Any | None:
"""Return a value, or None when the key does not exist."""
...
defupdate(self, key: str, value: Any) -> None:
"""Create or replace a value."""
...
classDatabaseSource:
"""Small dictionary example that represents database access."""def__init__(self) -> None:
self._rows: dict[str, Any] = {}
defretrieve(self, key: str) -> Any | None:
returnself._rows.get(key)
defupdate(self, key: str, value: Any) -> None:
self._rows[key] = value
classApiSource:
"""Small dictionary example that represents a remote API client."""def__init__(self) -> None:
self._remote_data: dict[str, Any] = {}
defretrieve(self, key: str) -> Any | None:
returnself._remote_data.get(key)
defupdate(self, key: str, value: Any) -> None:
self._remote_data[key] = value
classFileSource:
"""JSON file implementation of the same contract."""def__init__(self, path: Path) -> None:
self._path = path
self._lock = threading.Lock()
# Create an empty JSON object when the file is missing.ifnotself._path.exists():
self._write_all({})
def_read_all(self) -> dict[str, Any]:
withself._path.open("r", encoding="utf8") as file:
data = json.load(file)
ifnotisinstance(data, dict):
raise ValueError("The JSON file must contain an object")
return data
def_write_all(self, data: dict[str, Any]) -> None:
# Write a temporary file, flush it, and replace the target atomically.
temporary_path = self._path.with_name(self._path.name + ".tmp")
with temporary_path.open("w", encoding="utf8") as file:
json.dump(data, file, indent=2)
file.flush()
os.fsync(file.fileno())
os.replace(temporary_path, self._path)
defretrieve(self, key: str) -> Any | None:
withself._lock:
returnself._read_all().get(key)
defupdate(self, key: str, value: Any) -> None:
withself._lock:
data = self._read_all()
data[key] = value
self._write_all(data)
classDataSourceFactory:
"""Build a source from trusted application configuration.""" @staticmethoddefcreate(source_name: str, file_path: Path | None = None) -> DataSource:
if source_name == "database":
return DatabaseSource()
if source_name == "api":
return ApiSource()
if source_name == "file":
if file_path isNone:
raise ValueError("file_path is required for the file source")
return FileSource(file_path)
raise ValueError(f"Unsupported source: {source_name}")
classDataAccessService:
"""Delegate operations to the source that is active at call time."""def__init__(self, source: DataSource) -> None:
self._source = source
self._source_lock = threading.Lock()
defset_source(self, source: DataSource) -> None:
# Replace the Strategy without exposing source details to callers.withself._source_lock:
self._source = source
def_current_source(self) -> DataSource:
# Capture one source for the complete operation.withself._source_lock:
returnself._source
defretrieve(self, key: str) -> Any | None:
source = self._current_source()
return source.retrieve(key)
defupdate(self, key: str, value: Any) -> None:
source = self._current_source()
source.update(key, value)
defmain() -> None:
record = {"name": "Asha", "status": "active"}
# Start with the database source.
service = DataAccessService(DataSourceFactory.create("database"))
service.update("customer_1", record)
print("Database:", service.retrieve("customer_1"))
# Change to the API source at runtime.
service.set_source(DataSourceFactory.create("api"))
service.update("customer_1", record)
print("API:", service.retrieve("customer_1"))
# Change to the file source at runtime.
file_path = Path("customer_data.json")
service.set_source(DataSourceFactory.create("file", file_path=file_path))
service.update("customer_1", record)
print("File:", service.retrieve("customer_1"))
# Remove the demonstration file.
file_path.unlink(missing_ok=True)
if __name__ == "__main__":
main()
Where it is used
This design is useful when development uses a file, tests use a fake source, and production uses a database or API. It also supports tenant specific routing, staged migrations, offline mode, disaster recovery, feature controlled cutovers, and gradual replacement of an older storage system. The service remains unchanged while the application composition layer selects and injects the correct source.
Why Interviewers Ask This
Interviewers ask this question to test whether a candidate can separate business logic from storage details using Python objects and clear contracts. It also tests dependency injection, structural typing with Protocol, object composition, safe runtime replacement, testability, resource ownership, and judgment about concurrency and production failure handling.
Common interview mistakes
A common mistake is placing database, API, and file conditions inside every business method. That couples business logic to storage details. Another mistake is giving each source different return shapes, missing value rules, or exception behavior. Callers need one clear contract. The factory should build objects at the application boundary, not appear throughout business code. A shared service should not switch sources without synchronization. It is also unsafe to close an old database or API client immediately when an active operation may still be using it. Finally, the sample JSON file approach is not suitable for large data because every update reads and rewrites the complete file.
Interview tip
Explain the design in this order: one contract, three source implementations, constructor injection, factory creation, and Strategy replacement. Then state that each operation captures one source, and mention consistent errors, resource cleanup, database transactions, API timeouts, and the linear cost of rewriting a JSON file.
Interviewer may ask next
What happens if the source changes while another operation is running?
The running operation continues with the source object it captured at its start. DataAccessService reads the source under a lock and then releases the lock before calling retrieve or update. This matters because one operation cannot begin on one source and finish on another. Later operations may use the replacement source. The main limitation is resource cleanup. The application must not close the old database or API client until operations that captured it have finished.
When should the application use a Factory instead of injecting a source directly?
The application should use a Factory when trusted configuration must be converted into a DatabaseSource, ApiSource, or FileSource. The service should still receive the completed object through dependency injection. This matters because credentials, paths, connection pools, and client settings stay outside business logic. Direct injection is simpler in tests and when the caller already owns the source. The tradeoff is that a large factory can become difficult to maintain, so construction logic should remain small or be split into focused provider functions.
4. Implement Open the Lock in Python using breadth-first search.Language SpecificMediumMicrosoft
i Question Details
Treat the initial lock state as the root, treat dead-end combinations as blocked states, and return the minimum number of moves needed to reach the target or report that it is unreachable.
Short Interview Answer (30-60 seconds)
I would use breadth first search because every wheel turn has the same cost, so states are explored in increasing move count. I would store states in a deque, keep dead ends and visited states in sets, generate the eight states reachable by one wheel turn, and mark each valid state visited when I add it to the queue. The first time the target is reached gives the minimum number of moves.
Use breadth first search because every wheel turn costs one move. The initial state is 0000. Each lock combination is a graph state, and turning one wheel creates a neighboring state. A deque stores states waiting to be processed. Python deque supports constant time removal from the left, unlike removing the first item from a list.
Useful Questions to Ask the Interviewer
Should I focus on Python language behavior, or also explain the runtime and standard library?
Which Python version and execution environment should I assume?
Would you like a small code example together with production tradeoffs and edge cases?
Store dead ends in a set for fast membership checks. Return negative one if 0000 or the target is blocked. Return zero if the unblocked target is already 0000. Add 0000 to the queue and visited set. For each state, turn each of four digits once forward and once backward, including wraparound between 0 and 9. This creates eight neighbors. Python strings are immutable, so each neighbor is created with slicing.
Add only neighbors that are not blocked and not visited. Mark each neighbor visited before adding it to the queue. This prevents duplicate queue entries. Return when the target is found. If the queue becomes empty, return negative one. There are only ten thousand possible states. Since each state creates eight fixed length neighbors, time and memory are both bounded by the state space.
Example
The code converts dead ends to a set so blocked state checks are fast. It rejects a blocked initial state and a blocked target before starting the search. It uses collections.deque because removing an item from the left is constant time. Each queue item stores a lock state and the number of moves used to reach it. For every state, the code creates eight neighbors by changing one of four digits forward or backward with wraparound. Python strings cannot be changed in place, so slicing creates each new state. A valid neighbor is marked visited before it enters the queue, which prevents duplicate entries. Breadth first search processes states in increasing move count, so the first valid target match is the minimum. At most ten thousand states can be visited. Each state creates eight strings of fixed length four, so the time cost is O(10000) and the memory cost is O(10000).
Code
from collections import deque
from typing importListdefopen_lock(deadends: List[str], target: str) -> int:
# Store blocked combinations in a set for fast membership checks.
blocked = set(deadends)
start = "0000"# The search cannot start when the initial state is blocked.if start in blocked:
return -1# A blocked target cannot be reached as a valid lock state.if target in blocked:
return -1# No wheel turns are needed when the lock already shows the target.if target == start:
return0# Each queue item stores a state and the moves used to reach it.
queue = deque([(start, 0)])
# Mark a state visited when it enters the queue.
visited = {start}
while queue:
state, moves = queue.popleft()
# Turn each of the four wheels in both directions.for index inrange(4):
current_digit = int(state[index])
for change in (1, -1):
# Modulo provides wraparound between 0 and 9.
next_digit = (current_digit + change) % 10# Python strings are immutable, so build a new state.
next_state = state[:index] + str(next_digit) + state[index + 1 :]
# Ignore blocked states and states already added before.if next_state in blocked or next_state in visited:
continue# This is the minimum move count because the search is level based.if next_state == target:
return moves + 1# Mark the state before adding it to prevent duplicates.
visited.add(next_state)
queue.append((next_state, moves + 1))
# The queue is empty, so the target is unreachable.return -1if __name__ == "__main__":
sample_deadends = ["0201", "0101", "0102", "1212", "2002"]
sample_target = "0202"print(open_lock(sample_deadends, sample_target))
Where it is used
This pattern is useful when a Python program must find the fewest equal cost actions between states. Examples include puzzle solvers, workflow transition checks, device configuration tools, small routing problems, and validation of the shortest allowed sequence of state changes. Breadth first search is appropriate when every transition has the same cost and the complete state space is small enough to store visited states.
Why Interviewers Ask This
Interviewers ask this question to test whether a candidate can model combinations as graph states, choose breadth first search for minimum moves, use deque and set correctly, generate neighboring states safely, and explain Python runtime costs for string creation, queue operations, membership checks, and memory use.
Common interview mistakes
Common mistakes include using a list and removing its first item, which shifts the remaining elements, and marking a state visited only after removing it from the queue, which allows duplicate entries. Other mistakes include forgetting wraparound, trying to modify a Python string in place, allowing dead ends into the queue, returning the target before checking whether it is blocked, missing the blocked initial state, and using depth first search even though it does not guarantee minimum moves.
Interview tip
Start by saying that every wheel turn has equal cost, so breadth first search guarantees minimum moves. Then explain why deque is used, why dead ends and visited states are sets, how the eight neighbors are created, and why each state is marked visited when it enters the queue.
Interviewer may ask next
What should happen if 0000 or the target is in the dead ends?
Return negative one if 0000 is blocked because the search cannot begin from a valid state. Also return negative one if the target is blocked because a dead end cannot be accepted as a reachable result. These checks must happen before the normal search, and the blocked initial state check must happen before returning zero for target 0000.
Would bidirectional breadth first search improve the performance?
Yes, bidirectional breadth first search can explore fewer states by searching from 0000 and the target at the same time until the two visited sets meet. It preserves the same equal cost shortest path behavior and must still exclude dead ends. The main tradeoff is greater implementation complexity because two frontiers, two visited sets, and the meeting distance must be managed correctly.
5. How would you structure spiral-matrix traversal with Strategy and Factory patterns in Python?Language SpecificMediumMicrosoft
i Question Details
Model the matrix and traversal behind abstractions such as Matrix, TraversalStrategy, and SpiralTraversal, then explain how a factory can select additional traversal orders without changing client code.
Short Interview Answer (30-60 seconds)
I would keep the rectangular data in a Matrix class, place each traversal order behind a TraversalStrategy interface, and implement the boundary logic in SpiralTraversal. Client code asks TraversalFactory for a named strategy and calls traverse. Adding another registered strategy does not change the client flow. Spiral traversal still visits every matrix cell exactly once.
I would separate matrix storage from traversal behavior. Matrix copies the input rows, verifies that every row has the same length, and provides row count, column count, and indexed reading. TraversalStrategy is an abstract base class with one traverse method. Python type hints describe the expected types, but they do not enforce the contract at runtime. The abstract base class prevents an incomplete strategy from being instantiated.
Useful Questions to Ask the Interviewer
Should I focus on Python language behavior, or also explain the runtime and standard library?
Which Python version and execution environment should I assume?
Would you like a small code example together with production tradeoffs and edge cases?
SpiralTraversal keeps four boundaries named top, bottom, left, and right. It reads the top row, right column, bottom row, and left column, then moves inward. Checks before the bottom and left passes prevent duplicates when a layer has only one row or one column.
TraversalFactory stores names mapped to strategy classes and creates a fresh object for each request. The client uses the same selection and traversal flow for spiral or row major order. Each traversal takes linear time because each cell is read once. Returning a list also uses linear result memory. This structure is useful when several traversal orders are required. For one fixed order in a small script, plain functions are usually simpler.
Example
The code defines Matrix as the owner of validated rectangular data. TraversalStrategy defines the common method required by every traversal class. SpiralTraversal uses four shrinking boundaries. RowMajorTraversal shows that another order can follow the same contract. TraversalFactory maps the names spiral and row_major to their classes and returns a fresh instance. It also validates newly registered classes. Client code selects a name and calls the same traverse method. For the example matrix containing the values 1 through 9, spiral order returns 1, 2, 3, 6, 9, 8, 7, 4, 5. Empty matrices and matrices with zero columns return an empty list. The reverse passes are guarded so a single row or single column layer is never visited twice.
Code
from abc import ABC, abstractmethod
from collections.abc importSequencefrom typing import TypeAlias
Value: TypeAlias = intclassMatrix:
"""Store a rectangular integer matrix."""def__init__(self, rows: Sequence[Sequence[Value]]) -> None:
# Copy every row so later changes to the input do not affect this matrix.self._rows = [list(row) for row in rows]
# An empty matrix has zero columns.ifnotself._rows:
self._column_count = 0return# A valid rectangular matrix must have equal row lengths.self._column_count = len(self._rows[0])
ifany(len(row) != self._column_count for row inself._rows):
raise ValueError("Matrix rows must have equal length")
@propertydefrow_count(self) -> int:
returnlen(self._rows)
@propertydefcolumn_count(self) -> int:
returnself._column_count
defvalue_at(self, row: int, column: int) -> Value:
returnself._rows[row][column]
classTraversalStrategy(ABC):
"""Define the method supported by every traversal order.""" @abstractmethoddeftraverse(self, matrix: Matrix) -> list[Value]:
raise NotImplementedError
classSpiralTraversal(TraversalStrategy):
"""Visit the outer boundary, then move inward."""deftraverse(self, matrix: Matrix) -> list[Value]:
# A matrix with no cells has no traversal result.if matrix.row_count == 0or matrix.column_count == 0:
return []
result: list[Value] = []
top = 0
bottom = matrix.row_count - 1
left = 0
right = matrix.column_count - 1while top <= bottom and left <= right:
# Read the current top row from left to right.for column inrange(left, right + 1):
result.append(matrix.value_at(top, column))
top += 1# Read the current right column from top to bottom.for row inrange(top, bottom + 1):
result.append(matrix.value_at(row, right))
right -= 1# Read the bottom row only when an unread row remains.if top <= bottom:
for column inrange(right, left - 1, -1):
result.append(matrix.value_at(bottom, column))
bottom -= 1# Read the left column only when an unread column remains.if left <= right:
for row inrange(bottom, top - 1, -1):
result.append(matrix.value_at(row, left))
left += 1return result
classRowMajorTraversal(TraversalStrategy):
"""Visit each row from left to right."""deftraverse(self, matrix: Matrix) -> list[Value]:
result: list[Value] = []
for row inrange(matrix.row_count):
for column inrange(matrix.column_count):
result.append(matrix.value_at(row, column))
return result
classTraversalFactory:
"""Create traversal strategies by registered name."""
_strategies: dict[str, type[TraversalStrategy]] = {
"spiral": SpiralTraversal,
"row_major": RowMajorTraversal,
}
@classmethoddefregister(
cls,
name: str,
strategy_class: type[TraversalStrategy],
) -> None:
# Reject an empty lookup name.ifnot name:
raise ValueError("Strategy name cannot be empty")
# Type hints are not runtime validation, so check the class explicitly.ifnotisinstance(strategy_class, type) ornotissubclass(
strategy_class,
TraversalStrategy,
):
raise TypeError("Strategy class must inherit from TraversalStrategy")
cls._strategies[name] = strategy_class
@classmethoddefcreate(cls, name: str) -> TraversalStrategy:
try:
strategy_class = cls._strategies[name]
except KeyError as error:
raise ValueError(f"Unknown traversal strategy: {name}") from error
# Return a fresh strategy object for this request.return strategy_class()
defmain() -> None:
matrix = Matrix(
[
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]
)
# The selected name could come from configuration or user input.
selected_order = "spiral"
strategy = TraversalFactory.create(selected_order)
print(strategy.traverse(matrix))
if __name__ == "__main__":
main()
Where it is used
This structure is useful in systems that read the same rectangular data in several orders. Examples include image tile processing, board state analysis, report grids, matrix export tools, and automated test fixtures. Strategy classes are valuable when each order must be tested or changed independently. A factory is useful when the order comes from configuration, a command, or an application setting. It is unnecessary overhead when the program has only one small traversal function.
Why Interviewers Ask This
Interviewers ask this to see whether the candidate can separate data from behavior, define a clear Python interface, and use design patterns only when they make future changes safer. They also evaluate boundary handling, object responsibilities, factory selection, error handling, testability, and awareness that the patterns improve structure but do not reduce traversal cost.
Common interview mistakes
Common mistakes include putting storage, selection, and traversal logic in one class, using condition branches throughout client code, and forgetting the checks before the reverse passes. Missing those checks duplicates values in a single row or single column layer. Other mistakes include accepting rows with different lengths without defining their behavior, storing one shared mutable strategy instance, relying on type hints as runtime validation, and claiming that Strategy or Factory makes the traversal faster.
Interview tip
Explain the responsibilities before showing the loop. Matrix owns valid data. TraversalStrategy defines the contract. SpiralTraversal owns the boundary algorithm. TraversalFactory owns selection. Then walk through the 3 by 3 output, mention the duplicate prevention checks, and state the linear time and result memory costs.
Interviewer may ask next
How does SpiralTraversal handle an empty matrix, a single row, or a single column?
It handles all three cases without duplicate visits. An empty matrix or a matrix with zero columns returns an empty list. For a single row, the top pass reads every value and the guarded bottom pass is skipped. For a single column, the top pass reads its first value, the right pass reads the remaining values, and the guarded left pass is skipped. These boundary checks matter because reverse passes would otherwise read cells twice.
When would you return an iterator instead of a list?
I would return an iterator when callers can process values one at a time and avoiding a full result allocation matters. The current list contract uses linear result memory because it stores one output value for every cell. An iterator can reduce extra result memory to constant space apart from traversal state, but it changes the strategy contract. Callers must handle lazy execution, partial consumption, and errors that may occur during iteration.
6. Implement a SnapshotSet with snapshot iterator semantics in Python.Language SpecificMediumMicrosoft
i Question Details
Support add, remove, contains, and iterator operations. An iterator must preserve the set contents that existed when it was created even if the live set is modified later.
Short Interview Answer (30-60 seconds)
I would store the current values in a normal Python set. In __iter__, I would immediately make a shallow copy and return an iterator over that copy. Each iterator then keeps the membership that existed when it was created, while add, remove, and contains continue to use the live set. The main tradeoff is that every iterator creation copies all current values.
The practical solution is to keep one live Python set and copy it when an iterator is created. The add, remove, and contains operations use only the live set. The __iter__ method calls self._items.copy() and immediately returns an iterator over that copy. This timing matters. The snapshot must be captured when iter(snapshot_set) runs, not later when next is first called.
Useful Questions to Ask the Interviewer
Should I focus on Python language behavior, or also explain the runtime and standard library?
Which Python version and execution environment should I assume?
Would you like a small code example together with production tradeoffs and edge cases?
The copy is shallow. It creates a separate set membership table, but both sets still refer to the same element objects. Later add or remove calls do not change an existing iterator. This implementation uses discard for remove, so removing a missing value does nothing. Set iteration order is not guaranteed, so callers should compare contents rather than order.
Creating an iterator takes linear time and linear extra memory because every current reference is copied into a new set. Reading all values from the iterator also takes linear time. Add, remove, and contains are average constant time. This design works well when stable iteration matters more than copying cost. It is less suitable for very large sets or frequent iterator creation. It also provides no thread safety guarantee, so shared production use needs external synchronization.
Example
The class stores current values in self._items. add inserts a value into the live set. remove uses discard, so a missing value is ignored instead of raising KeyError. contains checks the live set, and __contains__ supports the Python in operator. __iter__ copies the live set at the exact moment iter is called and returns the copied set iterator. The iterator keeps the copied set alive internally, so the local snapshot variable can safely leave scope. In the example, the first iterator captures 1 and 2. Later changes make the live set contain 2 and 3, but the first iterator still produces 1 and 2. A second iterator created after those changes produces 2 and 3. The results are sorted only for stable display because set iteration order is not guaranteed.
Code
from collections.abc import Hashable, Iterator
from typing importGeneric, TypeVar
T = TypeVar("T", bound=Hashable)
classSnapshotSet(Generic[T]):
"""A set whose iterators preserve membership from creation time."""def__init__(self) -> None:
# Store the current live values.self._items: set[T] = set()
defadd(self, value: T) -> None:
# Add the value to the live set.self._items.add(value)
defremove(self, value: T) -> None:
# Ignore the request when the value is not present.self._items.discard(value)
defcontains(self, value: T) -> bool:
# Check membership in the current live set.return value inself._items
def__contains__(self, value: object) -> bool:
# Support the expression value in snapshot_set.return value inself._items
def__iter__(self) -> Iterator[T]:
# Capture membership now, when iter is called.
snapshot = self._items.copy()
# The returned iterator keeps the copied set alive.returniter(snapshot)
if __name__ == "__main__":
values = SnapshotSet[int]()
values.add(1)
values.add(2)
# Capture the first snapshot containing 1 and 2.
first_iterator = iter(values)
# Change only the live set and future snapshots.
values.remove(1)
values.add(3)
# Sort only to make the demonstration output predictable.print(sorted(first_iterator))
print(sorted(iter(values)))
print(values.contains(1))
print(2in values)
Where it is used
This pattern is useful for subscriber collections, active job identifiers, feature flag audiences, cache keys, and other in memory sets where one task must process a stable membership view while the live collection continues to change. It is a good fit for moderate set sizes and occasional iteration. It is a poor fit when the set is very large or when new iterators are created often because each iterator allocates and fills a complete set copy.
Why Interviewers Ask This
Interviewers ask this question to test whether a candidate understands Python iterators, set mutation, shallow copying, object lifetime, and the cost of preserving historical membership. It also tests whether the candidate notices that the snapshot must be captured when iter is called, not when the first item is requested.
Common interview mistakes
A common mistake is returning iter(self._items). That iterator reads the live set and can raise RuntimeError if the set size changes during iteration. Another mistake is using a generator whose body makes the copy. Generator code begins when next is first called, so that design can capture the snapshot too late. Deep copying every element is also unnecessary because the requirement protects membership, not the internal state of each object. Candidates may also promise stable ordering, but Python sets provide no ordering guarantee. Another mistake is using mutable objects whose hash or equality behavior changes after insertion, which can break normal set lookup behavior.
Interview tip
State the snapshot moment first. Explain that __iter__ copies the live set immediately and returns an iterator over that copy. Then mention shallow copy behavior, average constant time live operations, linear iterator creation cost, linear memory cost, and the lack of guaranteed iteration order.
Interviewer may ask next
What happens if an element object changes after the iterator is created?
The iterator still refers to the same element object because the set copy is shallow. The snapshot preserves membership, not a frozen copy of each object state. This matters because later changes inside a mutable but hashable object may be visible through the iterator. Fields used by the object hash or equality methods must not change while the object is stored in a set because that can break lookup behavior.
How does frequent iterator creation affect production performance?
Every __iter__ call copies the full current set. Iterator creation therefore takes linear time and linear extra memory in the number of live values. This matters for large sets or high request rates because repeated allocation increases memory use and garbage collection work. The tradeoff is simple and reliable snapshot membership in exchange for copying cost.
7. How would you implement new backend business logic in Python using object-oriented design?Language SpecificMediumMicrosoft
i Question Details
Identify the core domain objects and responsibilities, separate orchestration from business rules, and make the implementation testable and extensible inside an existing backend service.
Short Interview Answer (30-60 seconds)
I would model the main business concepts as small Python classes, keep important rules inside domain objects or policy classes, and use a service class to coordinate the workflow. I would pass repositories and other dependencies into the service instead of creating them inside it. This keeps the business logic independent from the web framework and makes it easier to test, change, and extend.
This question asks how to add a business feature to an existing Python backend without putting every rule in one large function or web handler. Before designing it, I would ask the interviewer:
Useful Questions to Ask the Interviewer
What business rules must always be enforced?
Which database and external services already exist?
What errors should callers receive?
Can the same request happen more than once?
How to Explain It in an Interview
I would first identify the domain objects. These are the main business concepts. For an order discount feature, they could be Order and DiscountPolicy.
Each class should have one clear responsibility. Order stores order state and protects rules about valid changes. DiscountPolicy calculates the allowed discount. OrderService coordinates the use case by loading the order, asking the policy for a value, applying the change, and saving the order.
I would pass the repository and policy into OrderService through its constructor. Python stores references to these objects on the service instance. Tests can therefore pass an in memory repository without opening a real database.
The web handler should only parse input, call the service, and translate known exceptions into responses. Database access and business rules should remain separate.
Important cases include a missing order, invalid state, repeated requests, and concurrent updates. A real implementation should use a transaction and locking or version checks when shared data changes. This design creates more Python objects than one large function, but object allocation is usually small compared with database or network work. The main benefit is clearer testing and safer extension.
Example
The example implements an order discount use case. Order is the domain object and owns the rules for changing its discount state. DiscountPolicy calculates the discount amount. OrderRepository is a Protocol that describes the storage operations required by the service without choosing a database. OrderService coordinates loading, calculating, applying, and saving. Its dependencies are passed through the constructor, so tests can use an in memory repository. The example rejects a second discount application and uses Decimal for money values. With the in memory repository, a lookup and save are normally constant time because they use a dictionary. The policy calculation and domain validation also use constant time and constant additional working memory for one order.
Code
from dataclasses import dataclass
from decimal import Decimal
from typing import Protocol
classOrderNotFoundError(Exception):
"""Raised when an order does not exist."""classInvalidOrderStateError(Exception):
"""Raised when a requested business action is not allowed."""@dataclassclassOrder:
"""Domain object that stores order data and protects valid state."""
order_id: str
subtotal: Decimal
customer_is_premium: bool
discount: Decimal = Decimal("0.00")
discount_applied: bool = Falsedefapply_discount(self, amount: Decimal) -> None:
# Prevent the same discount operation from changing the order twice.ifself.discount_applied:
raise InvalidOrderStateError("Discount was already applied")
# Reject an invalid negative discount amount.if amount < Decimal("0.00"):
raise ValueError("Discount cannot be negative")
# Prevent the discount from making the order total negative.self.discount = min(amount, self.subtotal)
self.discount_applied = Truedeftotal(self) -> Decimal:
# Return the amount due after the approved discount.returnself.subtotal - self.discount
classOrderRepository(Protocol):
"""Storage contract required by the business service."""defget(self, order_id: str) -> Order | None: ...
defsave(self, order: Order) -> None: ...
classDiscountPolicy:
"""Business rule that calculates the allowed discount."""defcalculate(self, order: Order) -> Decimal:
# Premium customers receive ten percent of the subtotal.if order.customer_is_premium:
return order.subtotal * Decimal("0.10")
return Decimal("0.00")
classOrderService:
"""Coordinates the use case without owning storage details."""def__init__(
self,
repository: OrderRepository,
discount_policy: DiscountPolicy,
) -> None:
# Keep explicit references to dependencies supplied by the caller.self.repository = repository
self.discount_policy = discount_policy
defapply_customer_discount(self, order_id: str) -> Decimal:
# Load the domain object through the repository contract.
order = self.repository.get(order_id)
if order isNone:
raise OrderNotFoundError(f"Order {order_id} was not found")
# Ask the policy object to calculate the business value.
discount = self.discount_policy.calculate(order)
# Ask the domain object to enforce its own valid state.
order.apply_discount(discount)
# Save the updated domain object through the repository contract.self.repository.save(order)
return order.total()
classInMemoryOrderRepository:
"""Simple repository used for tests and this runnable example."""def__init__(self, orders: list[Order]) -> None:
# Store orders by identifier for direct lookup.self.orders = {order.order_id: order for order in orders}
defget(self, order_id: str) -> Order | None:
returnself.orders.get(order_id)
defsave(self, order: Order) -> None:
self.orders[order.order_id] = order
defmain() -> None:
# Create sample domain data.
order = Order(
order_id="order_1001",
subtotal=Decimal("100.00"),
customer_is_premium=True,
)
# Build concrete dependencies outside the service.
repository = InMemoryOrderRepository([order])
policy = DiscountPolicy()
service = OrderService(repository, policy)
# Run the business use case.
final_total = service.apply_customer_discount("order_1001")
# A ten percent discount changes one hundred dollars to ninety dollars.print(final_total)
if __name__ == "__main__":
main()
Where it is used
This design is used for pricing rules, order validation, payment workflows, account permissions, subscription changes, inventory checks, and approval processes. It is especially useful when the same business logic must be called from an API endpoint, a background task, and an administrative command without copying the rules.
Why Interviewers Ask This
Interviewers ask this question to see whether a candidate can turn business requirements into clear Python objects with focused responsibilities. They are evaluating separation of concerns, dependency injection, type hints, error handling, testability, and production judgment. They also want to know whether the candidate can extend an existing backend service without coupling business rules to a web framework, database implementation, or external service.
Common interview mistakes
Common mistakes include placing every rule inside a Django or Flask handler, creating database connections inside the service, and building one large class that handles unrelated responsibilities. Other mistakes include exposing state changes without validation, using float for money, adding inheritance when a small policy object is enough, and testing only through slow integration tests. Production code must also define transaction boundaries and decide how repeated or concurrent requests are handled.
Interview tip
Start by naming the domain objects and the responsibility of each one. Then explain how the service coordinates them, how dependencies are injected, and how the design is tested. Use one concrete example and clearly separate framework code, storage code, and business rules.
Interviewer may ask next
What happens if two requests update the same order at the same time?
The current in memory example does not protect against concurrent requests. In a real database implementation, I would run the read and save inside a transaction and use row locking or an optimistic version field. This matters because two requests could read the same old state and both attempt a conflicting update. Row locking gives direct protection but can reduce concurrency. Version checks allow more concurrency but require conflict handling or a retry.
When would you use a function instead of a class for a business rule?
I would use a function when the rule is small, stateless, and has no injected dependencies. For example, a pure calculation that always maps the same inputs to the same output may be clearer as a typed function. I would use a policy class when the rule needs configuration, external dependencies, multiple implementations, or replacement during testing. A function has less structure, while a class provides a clearer extension point for behavior that can vary.
8. Explain deadlock cases and how to prevent them in concurrent Python code.Language SpecificHardMicrosoft
i Question Details
Define deadlock and its necessary conditions, give concrete cases including a circular wait with more than two locks, and discuss prevention, avoidance, detection, and recovery techniques with their tradeoffs.
Short Interview Answer (30-60 seconds)
The best prevention method is to define one global order for locks and always acquire them in that order. A deadlock happens when threads or tasks wait forever for resources held by one another. It requires mutual exclusion, holding a resource while waiting, no forced resource removal, and circular wait. The Python GIL does not prevent this. I also keep critical sections short, release locks with context managers, and use timeouts and monitoring to detect unexpected blocking.
Detailed Explanation
The practical rule is to give every lock a stable order and always acquire locks in that order. Suppose thread A holds lock 1 and waits for lock 2, thread B holds lock 2 and waits for lock 3, and thread C holds lock 3 and waits for lock 1. This three lock cycle cannot make progress.
Useful Questions to Ask the Interviewer
Should I focus on Python language behavior, or also explain the runtime and standard library?
Which Python version and execution environment should I assume?
Would you like a small code example together with production tradeoffs and edge cases?
A deadlock needs four conditions. A resource has one owner. A worker holds one resource while waiting for another. The runtime cannot safely take the resource away. A circular chain of waiting exists. Both threading.Lock and asyncio.Lock can be involved. The GIL does not prevent this because Python code can block while holding application locks.
Prevention removes one condition, usually circular wait through fixed ordering. Avoidance checks whether granting a resource would leave execution in a safe state, but it requires advance knowledge and extra bookkeeping. Detection uses timeouts, thread dumps, task stack inspection, health checks, or application wait tracking. Recovery can cancel an asyncio task, fail an operation, restart a worker, or restart the process. Python cannot safely force a thread to release a lock. Timeouts support detection and recovery, but correct ordering remains the main prevention method.
Where it is used
Deadlock prevention is important in Python services that update several shared caches, account records, files, queues, or in memory objects during one operation. It also matters in worker pools, background job systems, web servers using threads, and asyncio services where tasks share locks. Production systems commonly combine fixed lock ordering, short critical sections, context managers, timeout alerts, thread or task inspection, and worker restart policies.
Why Interviewers Ask This
Interviewers ask this question to test whether a candidate understands how Python threads and asynchronous tasks wait for shared resources. They also evaluate whether the candidate can recognize unsafe lock ordering, explain why the GIL does not prevent application deadlocks, and choose practical prevention, detection, and recovery controls for production systems.
Common interview mistakes
Common mistakes include assuming the GIL prevents deadlocks, acquiring the same locks in different orders, holding a lock during network or file input, calling unknown callback code while holding a lock, and failing to release a lock after an exception. Another mistake is treating a timeout as prevention. A timeout only limits waiting and may leave partial work that needs cleanup. Developers may also check only pairs of locks and miss circular waits involving three or more locks. Using threading.RLock can prevent one thread from blocking when it reacquires the same lock, but it does not prevent cycles involving different locks.
Interview tip
Start with fixed lock ordering as the practical answer. Then define deadlock, name the four necessary conditions, explain the three lock circular wait, and compare prevention, avoidance, detection, and recovery. State clearly that the GIL does not prevent deadlocks involving application locks.
Interviewer may ask next
Can one Python thread deadlock itself with a single lock?
Yes. A thread can deadlock itself when it acquires threading.Lock and then tries to acquire the same lock again before releasing it. A normal Lock is not reentrant, so the second acquisition waits for a release that the same blocked thread cannot perform. threading.RLock allows the owning thread to acquire the same lock repeatedly, but it must release the lock the same number of times. RLock solves this specific repeated acquisition case, but it does not prevent circular waits involving different locks.
What is the tradeoff of using lock acquisition timeouts in production?
A timeout improves detection and recovery, but it does not make an unsafe locking design correct. It limits how long a worker waits and can trigger logging, failure handling, retry, cancellation, or restart logic. The tradeoff is added complexity. Temporary contention may cause false failures, retries may repeat side effects, and partial work may require rollback or cleanup. Fixed lock ordering should remain the main prevention method, while timeouts act as an operational safety control.
9. Review a Python file for correctness, validation, credential exposure, and code-safety problems.Language SpecificHardMicrosoft
i Question Details
Review the supplied Python functions rather than rewriting them from scratch. Identify missing validation, credentials exposed through console output, and other correctness, quality, or safety issues.
Short Interview Answer (30-60 seconds)
I would review the existing functions in a fixed order. First, I would trace inputs, return values, exceptions, and side effects. Then I would add runtime validation at trust boundaries, remove credentials from print statements and logs, and inspect risky operations such as dynamic evaluation, shell execution, file access, and broad exception handling. I would make the smallest safe corrections and add tests for valid input, invalid input, and failure paths.
Detailed Explanation
The practical goal is to correct the supplied functions without rewriting the file. I would first trace every value from its source to where it is used. Python type hints describe expected types, but they do not enforce them at runtime. Public functions should therefore reject missing values, wrong types, invalid ranges, malformed text, and unexpected collection contents before performing sensitive work.
Useful Questions to Ask the Interviewer
Should I focus on Python language behavior, or also explain the runtime and standard library?
Which Python version and execution environment should I assume?
Would you like a small code example together with production tradeoffs and edge cases?
Next, I would inspect every print call, log message, and exception message for passwords, tokens, connection strings, personal data, or complete request objects. Secrets should come from environment variables or a secret manager, and logs should contain only safe context.
I would then check for mutable default arguments, missing return paths, inconsistent return types, bare or overly broad except blocks, swallowed exceptions, unsafe eval or exec calls, shell commands built from untrusted strings, path traversal, unclosed files, and unchecked external responses. Files should use context managers, exceptions should be specific, and error messages should not expose secrets.
Finally, I would add tests for normal input, boundary values, invalid types, missing credentials, external failures, and partial results. No source file was included here, so exact line findings cannot be stated.
Where it is used
This review method is used during pull requests, security reviews, production incident fixes, command line tools, web request handlers, data import jobs, automation scripts, and services that process untrusted input or credentials.
Why Interviewers Ask This
Interviewers ask this question to test whether a candidate can inspect existing Python code, trace runtime behavior, find missing validation, detect secret exposure, recognize unsafe operations, and recommend focused corrections without replacing the original design.
Common interview mistakes
Common mistakes include treating type hints as runtime validation, printing complete objects that contain secrets, storing credentials directly in source code, catching Exception and hiding the real failure, using mutable lists or dictionaries as default arguments, calling eval or exec on untrusted text, building shell commands with string formatting, accepting file paths without checking their resolved location, opening files without a context manager, ignoring failed external responses, and changing the whole design before proving which behavior is incorrect.
Interview tip
Explain the review in this order: trace data, validate inputs, protect secrets, inspect unsafe operations, verify exceptions and resources, then add tests. State clearly that exact defects must be tied to exact lines in the supplied file.
Interviewer may ask next
Do Python type hints reject an argument with the wrong type at runtime?
No. Standard Python type hints do not automatically reject a wrong value at runtime. A parameter annotated as int can still receive a string unless the function or a validation library checks it. This matters because data from users, files, environment variables, and network responses must be validated before it reaches arithmetic, file access, database operations, or other sensitive code.
What should change if a function prints an access token for debugging?
The access token must be removed from console output, logs, and exception messages. The function should log only safe context, such as a request identifier or a carefully redacted value when that is genuinely required. The token should be loaded from an environment variable or a secret manager instead of being stored in source code. The tradeoff is less convenient debugging, but it prevents credentials from leaking into terminals, log storage, monitoring systems, test output, and support exports.
10. Implement a quadtree for two-dimensional range queries in Python.Language SpecificHardMicrosoft
i Question Details
Given an overall boundary rectangle, a node capacity, points to insert, and query rectangles, implement a quadtree. Split nodes when capacity is exceeded, redistribute points, and return all inserted points inside each inclusive query rectangle in original insertion order.
Short Interview Answer (30-60 seconds)
I would store points in a quadtree and split a full leaf into four children before inserting the next point. Each inserted point also gets a sequence number. A range query skips nodes that do not intersect the query, collects matching points, and sorts them by sequence number so the result keeps original insertion order. I would use one consistent midpoint rule and a maximum depth to handle boundary points and repeated coordinates safely.
This question asks us to store points inside one large rectangle and find points inside smaller query rectangles. Before coding, I would ask:
Useful Questions to Ask the Interviewer
Are rectangle edges inclusive?
Can the same point appear more than once?
Should an outside point be rejected?
Must results keep insertion order?
May I use a maximum depth?
How to Explain It in an Interview
A quadtree begins as one leaf. It stores points until it reaches capacity. When another point arrives, the node creates four children, moves its points into them, and inserts the new point into one child.
The child choice must be consistent. This code sends midpoint values west or north. Child rectangles touch at the midpoint, but insertion chooses only one child, so a point is never stored twice.
A query first checks whether its rectangle intersects the node boundary. If not, the node is skipped. Otherwise, it checks stored points and visits children.
Tree traversal does not preserve insertion order. Each point stores a sequence number. Python sorts matches by that number before returning coordinates.
A maximum depth prevents endless splitting when duplicate points always enter the same child. With depth d and capacity c, insertion costs O(d times c) because a split can redistribute up to c points at each level. With constant capacity, this is O(d), and balanced depth is usually O(log n). A query costs O(v plus k log k), where v is visited work and k is matches. Memory is O(n plus m) for points and nodes.
Example
The implementation uses Rect for inclusive containment and intersection tests. QuadtreeNode keeps indexed points in a leaf until a new insertion would exceed capacity. It then creates four children and redistributes every stored point with one deterministic midpoint rule. Quadtree assigns a growing sequence number only after a successful insertion. A range query visits only intersecting nodes, collects matching indexed points, sorts them by sequence number, and returns coordinates in original insertion order. A maximum depth prevents endless splitting for duplicate or extremely close points. With tree depth d and node capacity c, insertion costs O(d times c) because each split can redistribute up to c points. With constant capacity, this becomes O(d), and balanced depth is usually O(log n). Query work is O(v plus k log k), where v is the number of visited nodes and stored points examined and k is the number of matches. Memory is O(n plus m), where n is the number of inserted points and m is the number of created tree nodes.
Code
from __future__ import annotations
from dataclasses import dataclass
from math import isfinite
from typing importList, Optional, Tuple@dataclass(frozen=True)classRect:
"""An axis aligned rectangle with inclusive outer edges."""
min_x: float
min_y: float
max_x: float
max_y: floatdef__post_init__(self) -> None:
values = (self.min_x, self.min_y, self.max_x, self.max_y)
ifnotall(isfinite(value) for value in values):
raise ValueError("Rectangle coordinates must be finite")
ifself.min_x > self.max_x orself.min_y > self.max_y:
raise ValueError("Rectangle minimum values must not exceed maximum values")
defcontains(self, x: float, y: float) -> bool:
"""Return True when the point is inside or on the rectangle edge."""returnself.min_x <= x <= self.max_x andself.min_y <= y <= self.max_y
defintersects(self, other: "Rect") -> bool:
"""Return True when two inclusive rectangles overlap or touch."""returnnot (
self.max_x < other.min_x
or other.max_x < self.min_x
orself.max_y < other.min_y
or other.max_y < self.min_y
)
@dataclass(frozen=True)classIndexedPoint:
"""A point plus its original insertion position."""
x: float
y: float
sequence: intclassQuadtreeNode:
"""One node in the quadtree."""def__init__(
self,
boundary: Rect,
capacity: int,
depth: int,
max_depth: int,
) -> None:
self.boundary = boundary
self.capacity = capacity
self.depth = depth
self.max_depth = max_depth
self.points: List[IndexedPoint] = []
self.children: Optional[List[QuadtreeNode]] = Nonedefinsert(self, point: IndexedPoint) -> bool:
"""Insert a point when it belongs to this node boundary."""ifnotself.boundary.contains(point.x, point.y):
returnFalseifself.children isNoneandlen(self.points) < self.capacity:
self.points.append(point)
returnTrueifself.children isNoneandself.depth >= self.max_depth:
self.points.append(point)
returnTrueifself.children isNone:
self._split()
old_points = self.points
self.points = []
for old_point in old_points:
inserted = self._insert_into_child(old_point)
ifnot inserted:
raise RuntimeError("Redistribution failed")
returnself._insert_into_child(point)
def_split(self) -> None:
"""Create four child rectangles."""
mid_x = (self.boundary.min_x + self.boundary.max_x) / 2
mid_y = (self.boundary.min_y + self.boundary.max_y) / 2
next_depth = self.depth + 1self.children = [
QuadtreeNode(
Rect(self.boundary.min_x, self.boundary.min_y, mid_x, mid_y),
self.capacity,
next_depth,
self.max_depth,
),
QuadtreeNode(
Rect(mid_x, self.boundary.min_y, self.boundary.max_x, mid_y),
self.capacity,
next_depth,
self.max_depth,
),
QuadtreeNode(
Rect(self.boundary.min_x, mid_y, mid_x, self.boundary.max_y),
self.capacity,
next_depth,
self.max_depth,
),
QuadtreeNode(
Rect(mid_x, mid_y, self.boundary.max_x, self.boundary.max_y),
self.capacity,
next_depth,
self.max_depth,
),
]
def_insert_into_child(self, point: IndexedPoint) -> bool:
"""Choose exactly one child with deterministic midpoint rules."""ifself.children isNone:
returnFalse
mid_x = (self.boundary.min_x + self.boundary.max_x) / 2
mid_y = (self.boundary.min_y + self.boundary.max_y) / 2
west = point.x <= mid_x
north = point.y <= mid_y
if north and west:
child_index = 0elif north:
child_index = 1elif west:
child_index = 2else:
child_index = 3returnself.children[child_index].insert(point)
defquery(self, area: Rect, found: List[IndexedPoint]) -> None:
"""Collect points inside the inclusive query rectangle."""ifnotself.boundary.intersects(area):
returnfor point inself.points:
if area.contains(point.x, point.y):
found.append(point)
ifself.children isnotNone:
for child inself.children:
child.query(area, found)
classQuadtree:
"""Public quadtree interface."""def__init__(self, boundary: Rect, capacity: int, max_depth: int = 20) -> None:
if capacity <= 0:
raise ValueError("Capacity must be greater than zero")
if max_depth < 0:
raise ValueError("Maximum depth must not be negative")
self.root = QuadtreeNode(boundary, capacity, 0, max_depth)
self.next_sequence = 0definsert(self, x: float, y: float) -> bool:
"""Insert one point and return whether insertion succeeded."""ifnot isfinite(x) ornot isfinite(y):
raise ValueError("Point coordinates must be finite")
point = IndexedPoint(x, y, self.next_sequence)
inserted = self.root.insert(point)
if inserted:
self.next_sequence += 1return inserted
defrange_query(self, area: Rect) -> List[Tuple[float, float]]:
"""Return matching points in original insertion order."""
found: List[IndexedPoint] = []
self.root.query(area, found)
found.sort(key=lambda point: point.sequence)
return [(point.x, point.y) for point in found]
defmain() -> None:
tree = Quadtree(Rect(0, 0, 100, 100), capacity=2)
points = [(10, 10), (70, 20), (30, 40), (90, 90), (50, 50), (30, 40)]
for x, y in points:
inserted = tree.insert(x, y)
print(f"Inserted {(x, y)}: {inserted}")
queries = [
Rect(0, 0, 50, 50),
Rect(25, 35, 80, 60),
]
for area in queries:
print(tree.range_query(area))
if __name__ == "__main__":
main()
Where it is used
Quadtrees are used for map searches, game collision checks, nearby object lookup, geographic data, image regions, and other spatial indexes. They are useful when points are spread across a two dimensional area and most queries cover only part of it. They are less effective when most points fall into one tiny region or when nearly every query covers the full boundary. Production code should validate finite numeric coordinates, capacity, depth, and rectangle bounds.
Why Interviewers Ask This
Interviewers ask this question to test whether a candidate can build a recursive Python data structure, divide a two dimensional area correctly, manage mutable collections, and preserve an ordering requirement that tree traversal does not naturally provide. It also tests careful handling of inclusive boundaries, duplicate points, degenerate data, recursion depth, performance, memory use, and production validation.
Common interview mistakes
Common mistakes include placing a midpoint point into more than one child, forgetting to redistribute existing points after a split, and using exclusive comparisons even though query edges are inclusive. Another mistake is assuming recursive traversal preserves insertion order. It does not, so sequence numbers are required. Implementations may also split forever for duplicate points, accept invalid capacities, ignore outside points without documenting the behavior, or claim every operation is always logarithmic.
Interview tip
State the boundary and duplicate rules first. Then explain splitting, redistribution, intersection pruning, and sequence numbers in that order. Mention the maximum depth and give both average and worst case costs instead of claiming guaranteed logarithmic performance.
Interviewer may ask next
What happens when many duplicate points have exactly the same coordinates?
They remain separate inserted points and follow the same deterministic child path. When the maximum depth is reached, that leaf keeps additional points even after it exceeds capacity. This prevents endless splitting, which matters because another split cannot separate identical coordinates. The tradeoff is that queries touching that leaf may scan many stored points.
How could you reduce the cost of restoring insertion order for very large query results?
The current code sorts k matches by sequence number, so that part costs O(k log k). A more complex version could keep node results ordered and merge ordered child results during the query. This change can reduce final ordering work, but it adds implementation complexity and may increase insertion work or memory use. The existing sort is simpler and is often the better production choice unless large result sets are common.
More questions load as you scroll
Python Developer Resume Examples
Explore the resume examples below to find the one that best matches your target Python Developer role.
Disclaimer: This interview guide is for educational and informational purposes only. It is designed to help readers prepare, but it does not guarantee any interview result, hiring decision, offer, or outcome. Interview questions, hiring criteria, and preferred answers can vary by employer, interviewer, industry, location, and time. The examples and explanations reflect the authors' research and judgment, are provided without warranties of any kind, and should not be treated as the only correct approach. Diagrams are simplified illustrations intended to highlight the main components and their interactions; actual systems and implementations may be more complex. Alternative approaches may be equally valid or better suited to a particular question, context, or interviewer. To the fullest extent permitted by applicable law, the author, contributors, and publisher are not liable for decisions made, actions taken, or losses incurred based on this guide.
Company Notice: This guide is an independent educational resource and is not affiliated with, endorsed by, sponsored by, or approved by the company named in this guide. Company names are used only to identify interview experiences commonly reported by candidates. Interview practices can change without notice, and inclusion of company-specific content does not mean these questions are official, complete, or guaranteed to be asked. To the fullest extent permitted by law, the author, contributors, and publisher are not responsible for outcomes related to use of this material.