You are building a task assignment system for a support platform. The system needs to distribute incoming tasks to available workers based on various criteria that evolve across multiple parts of the problem.
The problem is divided into four progressive parts, each introducing additional complexity:
Load Balancing: Assign tasks to workers based on current workload (least loaded first)
Specialty Matching: Match tasks to workers who have the required specialty
Account Affinity: Prioritize workers who have previously handled tasks from the same account
Worker Availability: Handle workers going offline dynamically
There are multiple parts to this problem -- ask the interviewer how many parts there are to better manage your time
Start with the simplest data structure and extend it as parts get more complex
Write your own test cases and ensure your code compiles and runs correctly
Implement a function assign_tasks(workers, tasks) that assigns each task to the worker with the least current workload.
Rules:
Process tasks in the order they appear in the input
Assign each task to the worker with the minimum total workload at that moment
If multiple workers have the same workload, assign to the worker who appears first in the workers list
A worker's workload increases by the task's duration when assigned a task
workers = ["alice", "bob", "charlie"] tasks = [ {"id": "task1", "duration": 5}, {"id": "task2", "duration": 3}, {"id": "task3", "duration": 7}, {"id": "task4", "duration": 2}, {"id": "task5", "duration": 4}, ]
[ {"taskId": "task1", "worker": "alice"}, # all at 0, alice first in list {"taskId": "task2", "worker": "bob"}, # bob and charlie at 0, bob first {"taskId": "task3", "worker": "charlie"}, # charlie at 0 (lowest) {"taskId": "task4", "worker": "bob"}, # bob at 3 (lowest) {"taskId": "task5", "worker": "alice"}, # alice and bob tied at 5, alice first ]
Initially all workers have workload 0. task1 goes to alice (first in list)
alice: 5, bob: 0, charlie: 0. task2 goes to bob (first with minimum 0)
alice: 5, bob: 3, charlie: 0. task3 goes to charlie (only one with 0)
alice: 5, bob: 3, charlie: 7. task4 goes to bob (minimum workload 3)
alice: 5, bob: 5, charlie: 7. task5 goes to alice (tied at 5, alice first in list)
Process tasks sequentially in input order
Track cumulative workload per worker
Use worker list order as tiebreaker
Return assignments in task processing order
Among eligible workers (those with a matching specialty), assign to the one with the least workload (using the same tiebreaker rules as Part 1).
` workers = [ {"name": "alice", "specialty": "billing"}, {"name": "bob", "specialty": "technical"}, {"name": "charlie", "specialty": "billing"}, {"name": "diana", "specialty": "technical"}, ]
tasks = [ {"id": "task1", "duration": 5, "requiredSpecialties": ["billing"]}, {"id": "task2", "duration": 3, "requiredSpecialties": ["technical"]}, {"id": "task3", "duration": 7, "requiredSpecialties": ["billing", "technical"]}, {"id": "task4", "duration": 2, "requiredSpecialties": ["billing"]}, {"id": "task5", "duration": 4, "requiredSpecialties": ["technical"]}, ] `
[ {"taskId": "task1", "worker": "alice"}, # billing: alice(0), charlie(0) -> alice first {"taskId": "task2", "worker": "bob"}, # technical: bob(0), diana(0) -> bob first {"taskId": "task3", "worker": "charlie"}, # all eligible: charlie(0) and diana(0) lowest, charlie first {"taskId": "task4", "worker": "alice"}, # billing: alice(5), charlie(7) -> alice lower {"taskId": "task5", "worker": "diana"}, # technical: bob(3), diana(0) -> diana lower ]
Filter workers by specialty before applying workload-based selection
A task can be assigned to any worker whose specialty is in the task's requiredSpecialties list
If no worker matches the required specialties, skip the task
Continue using workload-based selection among eligible workers
Among eligible workers (matching specialty), prefer workers who have handled the same account before
Among workers with account affinity, choose the one with the least workload
If no worker has affinity, fall back to least workload among eligible workers
Use worker list order as the final tiebreaker
` workers = [ {"name": "alice", "specialty": "billing"}, {"name": "bob", "specialty": "billing"}, {"name": "charlie", "specialty": "billing"}, ]
tasks = [ {"id": "task1", "duration": 5, "requiredSpecialties": ["billing"], "accountId": "acme"}, {"id": "task2", "duration": 3, "requiredSpecialties": ["billing"], "accountId": "globex"}, {"id": "task3", "duration": 7, "requiredSpecialties": ["billing"], "accountId": "acme"}, {"id": "task4", "duration": 2, "requiredSpecialties": ["billing"], "accountId": "acme"}, {"id": "task5", "duration": 4, "requiredSpecialties": ["billing"], "accountId": "globex"}, ] `
[ {"taskId": "task1", "worker": "alice"}, # no affinity yet; all at 0, alice first {"taskId": "task2", "worker": "bob"}, # no affinity; bob and charlie at 0, bob first {"taskId": "task3", "worker": "alice"}, # alice has acme affinity -> prioritized {"taskId": "task4", "worker": "alice"}, # alice still has acme affinity {"taskId": "task5", "worker": "bob"}, # bob has globex affinity ]
task1 (acme): No one has handled acme yet. All at workload 0, alice first -> alice
task2 (globex): No one has handled globex. alice: 5, bob: 0, charlie: 0 -> bob
task3 (acme): alice handled acme before, so she is prioritized -> alice
task4 (acme): alice still has acme affinity -> alice
task5 (globex): bob handled globex before -> bob
Track which accounts each worker has handled
Prioritize workers with account affinity over workload balancing
If multiple workers have affinity for the same account, choose by lowest workload
Fall back to Part 2 logic when no worker has affinity
Extend the system to handle workers going offline. Workers can become unavailable at any point, and the system must adapt.
New input: An additional parameter offline_events specifies when workers go offline:
offline_events = [ {"worker": "alice", "afterTaskIndex": 2} # alice goes offline after task index 2 ]
They can no longer receive new task assignments
Their account affinity data is preserved
Tasks must be redistributed among remaining available workers
` workers = [ {"name": "alice", "specialty": "billing"}, {"name": "bob", "specialty": "billing"}, {"name": "charlie", "specialty": "billing"}, ]
tasks = [ {"id": "task1", "duration": 5, "requiredSpecialties": ["billing"], "accountId": "acme"}, {"id": "task2", "duration": 3, "requiredSpecialties": ["billing"], "accountId": "globex"}, {"id": "task3", "duration": 7, "requiredSpecialties": ["billing"], "accountId": "acme"}, {"id": "task4", "duration": 2, "requiredSpecialties": ["billing"], "accountId": "acme"}, {"id": "task5", "duration": 4, "requiredSpecialties": ["billing"], "accountId": "globex"}, ]
offline_events = [ {"worker": "alice", "afterTaskIndex": 2} ] `
Track worker availability status
Process offline events after the specified task index
Skip unavailable workers during assignment
Handle edge cases: all workers offline, no eligible workers available