Coding questions · DoorDash
Debug and refactor a legacy module
This question evaluates debugging, refactoring, naming and exception handling, input validation, code structure, and concurrency-safety design skills within a legacy codebase, requiring both hands-on code changes and higher-level reasoning about immutability, confinement, synchronization, and concurrent data structures.
Given a legacy module named DasherPicker and a failing test suite, systematically debug and refactor the code while following a provided review checklist. Identify and fix issues in naming conventions, exception handling, input validation, and code structure so that all tests pass. Then outline a plan to make the module safe for multi-threaded execution (e.g., immutability, confinement, synchronization, concurrent data structures) and describe how you would validate correctness under concurrency (tests, stress tools, and diagnostics).
Part 1: Refactor DasherPicker Selection Logic
Refactor the DasherPicker selection logic so it assigns each delivery order to the single best eligible dasher, using deterministic tie-breaking, strict input validation, and no mutation of the caller's data.
Implement
def solution(dashers, orders):
...
Return a list of integers — one entry per order, in the same order the orders are given. Each entry is either the id of the chosen dasher or -1 if no dasher could be assigned. When orders is empty, the natural result is an empty list [] (there are no orders to assign).
Input format
dashers— a list of dasher records. Each record is[id, x, y, rating, capacity, active](a list or tuple), where:idis a positive, unique integerx,yare the dasher's coordinatesratingis the dasher's ratingcapacityis how many more orders the dasher can takeactiveis1if the dasher is active,0if inactive
orders— a list of orders. Each order is[x, y, min_rating](a list or tuple), wherex,yare the order's location andmin_ratingis the minimum rating required.
What to do
Process the orders from left to right (first to last). For each order, choose exactly one eligible dasher.
A dasher is eligible for an order when all of the following hold:
- The dasher is active (
active == 1). - The dasher has remaining capacity greater than
0. - The dasher's
ratingis at least the order'smin_rating(rating >= min_rating).
Among the eligible dashers, pick the best one by this priority:
- Smallest Manhattan distance to the order location, where distance
= |dasher_x - order_x| + |dasher_y - order_y|. - If still tied, the higher
rating. - If still tied, the smaller
id.
Append the chosen dasher's id to the result. Then decrease only that dasher's remaining capacity by 1 (tracked internally, so later orders see the updated capacity). If no dasher is eligible for an order, append -1 for that order and leave all capacities unchanged.
Important rules
- Do not mutate the input. Capacity must be tracked on internal copies; the original
dasherslist and its records must be unchanged when the function returns. - Each order is independent only in choice — capacity consumed by earlier orders carries forward to later orders.
Validation — return [] on any invalid input
If any part of the input is malformed or out of range, return an empty list [] instead of raising an exception. (Note that [] is also the natural result when orders is empty — see the examples below.) The input is invalid if any of these fail:
dashersandordersmust each be a list.- Every dasher record must be exactly 6 integers:
[id, x, y, rating, capacity, active]. Booleans are not accepted as integers. - Every order record must be exactly 3 integers:
[x, y, min_rating]. Booleans are not accepted as integers. - Dasher
idvalues must be positive and unique (a duplicate id makes the whole input invalid). - All coordinates
x,y(for both dashers and orders) must satisfy-10^6 <= x, y <= 10^6. ratingandmin_ratingmust satisfy0 <= value <= 500.capacity >= 0.activemust be either0or1.
Constraints
0 <= len(dashers) <= 10000 <= len(orders) <= 1000
Examples
dashers = [[1, 0, 0, 500, 1, 1], [2, 10, 0, 400, 2, 1]],orders = [[0, 0, 0], [1, 0, 0], [9, 0, 0], [0, 0, 450]]→[1, 2, 2, -1].- Order
[0,0,0]: dasher 1 is closest → assign1, its capacity drops to 0. - Order
[1,0,0]: dasher 1 is out of capacity, so dasher 2 is chosen →2(capacity now 1). - Order
[9,0,0]: dasher 2 is closest →2(capacity now 0). - Order
[0,0,450]: requires rating>= 450; dasher 1 is out of capacity and dasher 2's rating is too low →-1.
- Order
dashers = [[1, 0, 0, 500, 1, 1], [1, 1, 1, 400, 1, 1]],orders = [[0, 0, 0]]→[](duplicate dasher id — invalid input).dashers = [[1, 0, 0, 500, 1, 1]],orders = []→[](no orders, so the result is the empty assignment list).
Hints
- Turn the selection rule into a tuple comparison: distance, negative rating, then id.
- Copy the mutable dasher state before processing orders so that caller-owned input is not modified.
Approach
The solution separates validation from assignment, which is the crux of the "refactor" task.
Validation (fail-fast to []). A local is_int rejects bool (since True/False are int subclasses in Python). Every dasher record must be a length-6 list/tuple of ints with a positive, unique id (tracked in a seen set), coordinates within ±10^6, 0 ≤ rating ≤ 500, capacity ≥ 0, and active ∈ {0,1}. Orders are validated similarly (length 3, in-bounds coords, valid min_rating). Any malformed record makes the function return [] immediately instead of throwing.
Non-mutating copy. Each valid dasher is appended to workers as a fresh list [id, x, y, rating, capacity, active]. Capacity is decremented on these copies, so the caller's input is never mutated.
Greedy per-order selection. Orders are processed left to right. For each order, the code linearly scans workers, skipping any dasher that is inactive, out of capacity (capacity <= 0), or under-rated (rating < min_rating). For eligible dashers it builds the comparison key
(distance, -rating, dasher_id) where distance = |x-ox| + |y-oy| (Manhattan).
Picking the lexicographically smallest key encodes all three tie-breakers exactly: smallest distance first, then higher rating (via the negation), then smaller id. The winner's id is appended and its copied capacity is decremented by 1; if no dasher qualifies, -1 is appended.
This is correct because the key ordering mirrors the spec's priority list, and capacity is consumed per assignment so later orders see updated remaining capacity.
- Time complexity: O(O*D)
- Space complexity: O(D)
Part 2: Classify a Concurrency Safety Plan
Implement solution(resources, operations) that produces a deterministic concurrency-safety plan for a module being prepared for multi-threaded execution.
Instead of writing prose, you must encode a fixed classification rule: for each declared resource decide a single thread-safety strategy, then assemble a validation checklist.
Input
Two arguments, each a list:
resources— a list of[name, kind]records describing the module's resources.kindis one of:value— a scalar or immutable-style valuecounter— a numeric countercollection— a map/set/queue/cache/list-like resourcecomposite— a multi-field object whose fields may carry invariants
operations— a list of[resource_name, thread_id, op]records describing operations threads performed.opis one of:- Non-mutating:
read,iterate - Mutating:
write,inc,put,remove
- Non-mutating:
Each record may be given as a list or a tuple, but must have exactly the length shown (2 for a resource, 3 for an operation).
Output
Return a list of exactly two lists: [strategy_lines, validation].
strategy_lines
One entry per resource, in the same order the resources were given, formatted as the string "<name>:<strategy>" (the resource name, a colon, then the chosen strategy).
For each resource, pick the first strategy whose condition holds, checking the rules in this order:
immutable— the resource has no mutating operations.confined— the resource has at least one mutating operation, but every operation referencing it comes from a single (one)thread_id.atomic— the resource is acounter, is touched by more than one thread, and every mutating operation on it isinc.concurrent_collection— the resource is acollectiontouched by more than one thread (and is not covered by the rules above).synchronized— any remaining resource that is mutated and shared across more than one thread.
A resource counts as shared when more than one distinct thread_id appears in its operations. Only mutating operations count toward "has mutations"; read and iterate never make a resource mutable but they do count toward how many distinct threads touched it.
validation
A checklist list, built in this exact order:
- Always include
unit_tests. - If at least one resource was classified as
atomic,concurrent_collection, orsynchronized, append (in order)stress_tests, thenrace_detector. - If at least one resource was classified as
synchronized, appenddeadlock_diagnosticsnext. - If at least one resource was classified as
atomic,concurrent_collection, orsynchronized, appendcontention_metricslast.
(So the only sources added beyond unit_tests are tied to the presence of shared, mutable resources, and deadlock_diagnostics appears only when something is synchronized, positioned before contention_metrics.)
Invalid input
Return [[], ['invalid_input']] if any of the following is violated:
resourcesandoperationsmust each be a list, and every record must have the correct shape (a list/tuple of length 2 for[name, kind], a list/tuple of length 3 for[resource_name, thread_id, op]).- Each resource
namemust be a non-empty string and unique across resources. - Each resource
kindmust be one ofvalue,counter,collection,composite. - Every operation must reference an existing resource name.
- Each
thread_idmust be a non-empty string. - Each
opmust be one ofread,iterate,write,inc,put,remove.
Constraints
0 <= len(resources) <= 2000000 <= len(operations) <= 200000
Examples
Resource declared but never operated on (e.g. a counter with no operations) has no mutating operations, so it is immutable.
Empty input (resources = [], operations = []) is valid and returns [[], ['unit_tests']].
Hints
- For each resource, track the set of accessing threads and the list or count of mutating operation types.
- Apply the strategy rules in the given order; for example, a read-only counter is immutable, not atomic.
Approach
The solution makes two validating passes, then classifies each resource by a strict priority of rules.
Pass 1 — index resources. For each [name, kind] it checks the shape, that name is a non-empty unique string, and that kind is in {value, counter, collection, composite}. It records kinds[name], and initializes an empty threads_by_resource[name] set and an empty mutations_by_resource[name] list. Any violation returns [[], ['invalid_input']].
Pass 2 — fold operations. For each [resource_name, thread_id, op] it validates the shape, that the resource exists, that the thread id is a non-empty string, and that op is in the allowed set. Then it adds thread_id to that resource's thread set and, if op is mutating (write/inc/put/remove), appends it to the resource's mutation list. This collects exactly the two facts each rule needs: how many distinct threads touched it and which mutations occurred.
Classification (order matters). Per resource, in original order:
- no mutations →
immutable - mutated but
thread_count <= 1→confined - shared
counterwith every mutationinc→atomic - shared
collection→concurrent_collection - otherwise →
synchronized
Checking rules in this fixed order encodes the required precedence directly, so the first matching rule wins.
Checklist. Always unit_tests. If any resource is atomic/concurrent_collection/synchronized (shared-mutable), it also adds stress_tests, race_detector, and contention_metrics; if any is synchronized, it inserts deadlock_diagnostics before contention_metrics, matching the expected ordering.
It is correct because each strategy depends only on the aggregated thread-count and mutation facts, which are computed exhaustively before classification.
- Time complexity: O(R + O)
- Space complexity: O(R + O)