cswork / libraryOpen practice workspace

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:
    • id is a positive, unique integer
    • x, y are the dasher's coordinates
    • rating is the dasher's rating
    • capacity is how many more orders the dasher can take
    • active is 1 if the dasher is active, 0 if inactive
  • orders — a list of orders. Each order is [x, y, min_rating] (a list or tuple), where x, y are the order's location and min_rating is 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:

  1. The dasher is active (active == 1).
  2. The dasher has remaining capacity greater than 0.
  3. The dasher's rating is at least the order's min_rating (rating >= min_rating).

Among the eligible dashers, pick the best one by this priority:

  1. Smallest Manhattan distance to the order location, where distance = |dasher_x - order_x| + |dasher_y - order_y|.
  2. If still tied, the higher rating.
  3. 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 dashers list 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:

  • dashers and orders must 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 id values 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.
  • rating and min_rating must satisfy 0 <= value <= 500.
  • capacity >= 0.
  • active must be either 0 or 1.

Constraints

  • 0 <= len(dashers) <= 1000
  • 0 <= 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 → assign 1, 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.
  • 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

  1. Turn the selection rule into a tuple comparison: distance, negative rating, then id.
  2. 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. kind is one of:
    • value — a scalar or immutable-style value
    • counter — a numeric counter
    • collection — a map/set/queue/cache/list-like resource
    • composite — a multi-field object whose fields may carry invariants
  • operations — a list of [resource_name, thread_id, op] records describing operations threads performed. op is one of:
    • Non-mutating: read, iterate
    • Mutating: write, inc, put, remove

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:

  1. immutable — the resource has no mutating operations.
  2. confined — the resource has at least one mutating operation, but every operation referencing it comes from a single (one) thread_id.
  3. atomic — the resource is a counter, is touched by more than one thread, and every mutating operation on it is inc.
  4. concurrent_collection — the resource is a collection touched by more than one thread (and is not covered by the rules above).
  5. 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, or synchronized, append (in order) stress_tests, then race_detector.
  • If at least one resource was classified as synchronized, append deadlock_diagnostics next.
  • If at least one resource was classified as atomic, concurrent_collection, or synchronized, append contention_metrics last.

(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:

  • resources and operations must 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 name must be a non-empty string and unique across resources.
  • Each resource kind must be one of value, counter, collection, composite.
  • Every operation must reference an existing resource name.
  • Each thread_id must be a non-empty string.
  • Each op must be one of read, iterate, write, inc, put, remove.

Constraints

  • 0 <= len(resources) <= 200000
  • 0 <= 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

  1. For each resource, track the set of accessing threads and the list or count of mutating operation types.
  2. 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 counter with every mutation inc → 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)