Why Online Scheduling / Load Balancing Matters
Imagine a service with several identical workers and a stream of tasks. Each task reveals how much processing it needs when it is submitted, and the router must immediately commit it to one worker. It cannot move old tasks when a larger one appears later. The objective is to finish the whole collection as early as possible, which means minimizing the largest worker workload.
A natural rule is list scheduling: assign each newly revealed job to the worker with the least total work already committed to it. This greedy choice is cheap, causal and easy to inspect. It cannot predict future large jobs, so it can be worse than a hindsight schedule even when it makes the best immediate choice. Competitive analysis quantifies that loss.
We will study the classical online list model on identical machines, prove list scheduling's $2-1/m$ competitive guarantee, and construct an input that attains the bound. A complete simulation records every assignment, planned start/finish time, worker load, work/span lower bound and decision comparison. Python, C++ and Java use identical logic and produce identical output.
Task routing, batch-worker pools and distributed work queues provide useful motivations, but their actual constraints need checking before applying the theorem. Unknown job durations, release timestamps, machine speeds, data locality and migration change the model. The prerequisites on algorithm analysis and queues supply the cost and state reasoning needed to make these distinctions precise.
Online list scheduling commits each revealed job to a currently least-loaded identical worker, using no future jobs. Its worst-case makespan is at most $2-1/m$ times a hindsight optimum under the stated independent-job, known-duration, nonpreemptive model.
Core Concepts and Terminology
There are $m\ge1$ identical unit-speed workers, numbered 0 through $m-1$, and a finite sequence $\sigma=(p_1,\ldots,p_n)$ of independent jobs with nonnegative processing times. Job j reveals its duration $p_j$ when it is presented. Before the next job is revealed, the algorithm must assign j permanently to exactly one worker. A job cannot be split, preempted, rejected or migrated. Each worker executes its assigned jobs sequentially in assignment order.
Revelation order is not a clock. The list model has no release timestamps and no elapsed execution between reveal events. All jobs may be viewed as available at planned execution time zero; only their descriptions are revealed sequentially to the assignment policy. Thus an assignment made at the seventh reveal can have planned start time 2. This would not be valid for a job actually released at physical time 7. Online-over-time scheduling needs a different feasibility model.
Let $a_j$ be job j's assigned worker. After the first k assignments, the committed load is
$$L_i^{(k)}=\sum_{\substack{1\le j\le k\\a_j=i}}p_j,\qquad C_k=\max_{0\le i\lt m}L_i^{(k)}.$$
The final makespan is $C_{\max}=C_n$. A job appended to worker i has planned interval $[L_i,L_i+p_j)$, after which its load increases by $p_j$. Half-open intervals allow consecutive jobs to meet at one endpoint without overlap. A zero-duration job has an empty interval, contributes no load and still receives one assignment.
Work and intrinsic span for a prefix are
$$W_k=\sum_{j=1}^k p_j,\qquad S_k=\max_{1\le j\le k}p_j,$$
with both zero for an empty prefix. Jobs have no dependencies on each other, so the intrinsic critical path consists of one longest job and has length $S_k$. Ordering several jobs on one worker is a scheduling constraint, not an added intrinsic dependency in the input. For a real task DAG the span may be larger than its longest individual task, and readiness must also be respected.
The offline optimum knows the full collection and may choose another assignment, but uses the same m workers and the same nonpreemptive, indivisible jobs. Equivalently it partitions the jobs among workers to minimize the maximum sum:
$$\operatorname{OPT}(\sigma)=\min_{a_1,\ldots,a_n\in\{0,\ldots,m-1\}}\max_{0\le i\lt m}\sum_{j:a_j=i}p_j.$$
Any feasible schedule needs enough aggregate processing capacity and enough time for its longest indivisible job. Therefore
$$B_k=\max\left(\frac{W_k}{m},S_k\right)\le\operatorname{OPT}(\sigma_k)\le C_k.$$
B is a lower bound, not generally the optimum. On two workers with jobs $[3,3,3]$, $B=9/2$ but OPT=6 because one worker must receive two jobs. The program prints B as LB and $C_k/B_k$ as C/LB. When B is positive, this is an upper bound on the actual observed ratio $C_k/\operatorname{OPT}(\sigma_k)$, not a claimed exact ratio. In this example C/LB is $4/3$, while the actual ratio is 1. If all jobs are zero or no jobs arrive, both C and OPT are zero; the multiplicative inequality holds directly and the undefined ratio is printed as NA.
A deterministic policy is online if two streams with the same revealed prefix produce the same assignments on that prefix. It may know m and its own past state. Our controller receives only one duration per call; it receives neither a future array nor the final n. Ties among equal loads go to the smallest worker ID, making every trace reproducible.
| Term | Exact example | Role |
|---|---|---|
| Online list arrival | Duration 3 becomes known at the seventh reveal | Information event; not release time 7 |
| Identical worker | Every worker executes a size-3 job in 3 time units | Makes minimizing load equivalent to minimizing finish time |
| Committed load | $L=[2,2,2]$ before the large job | End of each worker's planned queue |
| Assignment | Put the new job on worker 0 | Irrevocable choice made before the next reveal |
| Makespan | Final loads $[5,2,2]$ give C=5 | Objective: last planned completion time |
| Work W | Six unit jobs and a size-3 job give W=9 | Aggregate capacity lower bound $W/m$ |
| Intrinsic span S | Longest independent job has length 3 | Lower bound that extra workers cannot remove |
| Prefix lower bound B | $\max(9/3,3)=3$ | Cheap certificate below OPT, not an optimizer |
| Offline optimum | One worker gets the size-3 job; two pack the six units | Full-information comparison with the same resources |
| Competitive factor | $C_{\max}\le(2-1/m)\operatorname{OPT}$ for every list | Worst-case quality guarantee, distinct from runtime |
| Tie rule | Loads $[2,2,2]$ select worker 0 | Keeps assignments deterministic across languages |
| Trace comparison count | Two load comparisons for m=3 | Measures decision work, not simulated execution time |
How Online Scheduling / Load Balancing Works, Step by Step
- Initialize one controller. Create m zero loads, zero jobs seen, zero total work, zero longest job, zero makespan and zero load-comparison count. The teaching programs accept integer m from 1 through 8, at most 64 jobs, and integer durations from 0 through 1,000,000. These caps bound the display and arithmetic; the theorem covers arbitrary finite lists and nonnegative durations under the abstract model.
- Reveal one current duration. Validate it before modifying state. The controller does not ask how many later jobs exist or what their sizes will be. A rejected duration or a 65th job leaves all existing assignments and counters unchanged.
- Scan worker loads in ID order. Initialize the candidate to worker 0. For each worker i from 1 to m−1, replace the candidate only when $L_i\lt L_{\mathrm{candidate}}$. Equal loads retain the earlier, smaller ID. This performs exactly m−1 load comparisons for every accepted job, even a zero-duration one.
- Append the whole job. Let selected worker be q, start s equal its old load and finish f equal $s+p_j$. Assign the interval $[s,f)$ to that worker and update only its load to f. The new job's duration is identical on every worker, so minimizing old load also minimizes its immediate completion time. This local fact does not claim a globally optimal assignment for the eventual list.
- Update counters and return a receipt. Increment the job count, add duration to work, update span as the largest revealed duration, and update makespan as the maximum of its prior value and f. Copy the new load vector into the receipt so later assignments cannot rewrite an earlier trace snapshot.
- Audit outside the decision. The driver formats work, span, makespan, B and C/B after the commitment. It can inspect the whole completed test input for verification, but neither future jobs nor offline results feed back into the controller. It prints exact reduced fractions rather than floating-point approximations.
For the sample's seventh job, prior loads are $[2,2,2]$ and duration is 3. Both later-worker comparisons fail to improve on worker 0. The job starts at planned time 2 and ends at 5, leaving loads $[5,2,2]$. Work rises from 6 to 9, span from 1 to 3, and makespan from 2 to 5. The separate lower-bound audit gives B=3. No old unit job moves to accommodate the new large job.
A binary min-heap keyed by the pair (load, worker ID) implements exactly the same rule in $O(1+\log m)$ worst-case update time. It needs one current entry per worker, removal of the minimum, then insertion with the increased load. The code uses a scan so every comparison and tie can be explained directly. A heap changes the decision data structure, not the schedule or competitive proof, provided it preserves the same pair ordering.
flowchart TD
A["Validate worker count and initialize zero loads"] --> B{"Another job is revealed?"}
B -->|No| C["Driver prints final audit"]
B -->|Yes| D["Read only current duration"]
D --> E{"Valid duration and trace capacity?"}
E -->|No| F["Reject without changing state"]
E -->|Yes| G["Candidate is worker zero; scan later worker IDs"]
G --> H{"Another worker to compare?"}
H -->|Yes| I{"Its load is strictly smaller?"}
I -->|Yes| J["Replace candidate"]
I -->|No| K["Retain candidate, including ties"]
J --> L["Advance scan index"]
K --> L
L --> H
H -->|No| M["Append job and update loads and counters"]
M --> N["Return copied receipt; driver audits revealed prefix"]
N --> B
Worked Example with a Full Trace
Take three workers and the revealed list $[1,1,1,1,1,1,3]$. The driver stores this sample, but the controller sees only one duration at a time. Worker IDs are 0, 1 and 2. Initially all loads, work, span and makespan are zero.
- J1, duration 1. Before $[0,0,0]$: compare worker 1's 0 with candidate 0 and worker 2's 0 with candidate 0; neither is strictly smaller. Choose worker 0 and interval $[0,1)$. After $[1,0,0]$, W=1, S=1, C=1, B=1, C/B=1.
- J2, duration 1. Before $[1,0,0]$: worker 1's 0 improves on candidate load 1; worker 2's 0 ties the new candidate, so retain worker 1. Assign $[0,1)$ there. After $[1,1,0]$, W=2, S=1, C=1, B=1, C/B=1.
- J3, duration 1. Before $[1,1,0]$: worker 1 ties candidate load 1, then worker 2's 0 improves it. Assign worker 2 interval $[0,1)$. After $[1,1,1]$, W=3, S=1, C=1, B=1, C/B=1.
- J4, duration 1. Before $[1,1,1]$: both comparisons tie, so choose worker 0 interval $[1,2)$. After $[2,1,1]$, W=4, S=1, C=2, $B=4/3$, and $C/B=3/2$.
- J5, duration 1. Before $[2,1,1]$: worker 1 improves from load 2 to 1, and worker 2 ties it. Assign worker 1 interval $[1,2)$. After $[2,2,1]$, W=5, S=1, C=2, $B=5/3$, and $C/B=6/5$.
- J6, duration 1. Before $[2,2,1]$: worker 1 ties candidate load 2, then worker 2 improves it to 1. Assign worker 2 interval $[1,2)$. After $[2,2,2]$, W=6, S=1, C=2, B=2, C/B=1.
- J7, duration 3. Before $[2,2,2]$: both comparisons tie and worker 0 remains selected. Assign $[2,5)$ there. After $[5,2,2]$, W=9, S=3, C=5, B=3, and $C/B=5/3$.
Each accepted job uses two comparisons, for 14 total. Each job appears in exactly one nonoverlapping interval on its assigned worker. All seven durations sum to 9, which equals the sum of final loads. The planned idle intervals on workers 1 and 2 from time 2 to 5 reflect the irreversible packing; they are not times when the online controller failed to process an arrival.
For this completed list, the lower bound is attained by a feasible offline packing: put J7 alone on one worker, J1/J2/J3 on another, and J4/J5/J6 on the third. Each worker load is 3. Since $B=3\le\operatorname{OPT}\le3$, OPT is exactly 3 and the actual observed ratio is $5/3=2-1/3$. This is a proof of optimality for this particular offline packing, not an assumption that B is always exact. For the fourth and fifth prefixes, indivisibility similarly makes OPT=2 even though the displayed rational lower bounds are below 2; their actual online ratios are 1.
If the same multiset were revealed with duration 3 first, least-load scheduling would give final loads $[3,3,3]$ and makespan 3. That is a different online input order. Sorting the entire original list before its first decision would use future information and would implement an offline heuristic instead.
Trace It by Hand
Pause after J6, when loads are $[2,2,2]$. Predict the selected worker and interval if the next duration is 3. Then, after committing that job, predict the next assignment if a new duration-1 job is revealed.
Reveal: duration 3 selects worker 0 and interval $[2,5)$, producing $[5,2,2]$. A later duration 1 selects worker 1, the smaller ID among the two workers with load 2, and interval $[2,3)$, producing $[5,3,2]$. Work becomes 10, span remains 3, makespan remains 5, $B=10/3$, and C/B becomes $3/2$. The hypothetical later job cannot revise the earlier assignment.
For a two-worker tight example, trace $[2,2,4]$. The first two jobs occupy different workers, then the final size-4 job goes to worker 0, giving loads $[6,2]$. Offline can pack the two size-2 jobs together and the size-4 job separately, so OPT=4 and the ratio is $3/2$. Finally try two zero-duration jobs: both choose worker 0 under the tie rule, all loads stay zero, and the ratio is NA.
Implementation in Python, C++, and Java
Each panel is a complete standalone program with the same seven-job sample. Run the Python panel with Python 3; compile the C++ panel as C++17; save the Java panel as Main.java, compile it and run Main. All three print exactly the output below. No external packages, threads or wall-clock timing are involved.
The controller's constructor receives only m, and its next-job method receives only the current duration. The complete sample list lives in the driver. The controller stores loads and prefix counters, returns a per-job receipt with an independent load snapshot, and never sorts, buffers or inspects future jobs. This interface makes the information boundary concrete.
The teaching API validates integer m from 1 through 8, duration from 0 through 1,000,000, and at most 64 accepted jobs. Python explicitly rejects booleans and non-integer values; C++ and Java use integer argument types. Validation occurs before any mutation. Arithmetic uses exact integers, with 64-bit loads/totals in C++ and Java. Under these caps total work is at most 64,000,000, so the displayed products and counters fit safely. Removing caps requires reviewing overflow and text costs.
The audit helper is separate from the choice rule. It computes the exact rational B using integer numerator $Q=\max(W,mS)$, so $B=Q/m$, and formats $C/B=mC/Q$. It does not solve the offline partition problem. A denominator of zero returns NA, covering empty and all-zero workloads without inventing a ratio. The final guarantee is the theorem's factor, not a measured running time or a computed optimum.
Each checks value counts only load comparisons in the worker-selection scan. It excludes validation, counter maxima, copying and formatting. The planned interval uses time units of processing, while the comparison count measures scheduler computation. Worker IDs start at zero; job IDs start at one. Receipts copy the loads so they remain valid as later jobs arrive; callers should treat controller state and returned receipts as read-only outside the supplied methods.
Identical expected output (each item below is one printed line):
m=3 tie=lowest worker ID; online list modelINIT loads=[0,0,0] W=0 span=0 Cmax=0 LB=0/1 C/LB=NAJ1 p=1 -> W0 [0,1) loads=[1,0,0] W=1 span=1 Cmax=1 LB=1/1 C/LB=1/1 checks=2J2 p=1 -> W1 [0,1) loads=[1,1,0] W=2 span=1 Cmax=1 LB=1/1 C/LB=1/1 checks=2J3 p=1 -> W2 [0,1) loads=[1,1,1] W=3 span=1 Cmax=1 LB=1/1 C/LB=1/1 checks=2J4 p=1 -> W0 [1,2) loads=[2,1,1] W=4 span=1 Cmax=2 LB=4/3 C/LB=3/2 checks=2J5 p=1 -> W1 [1,2) loads=[2,2,1] W=5 span=1 Cmax=2 LB=5/3 C/LB=6/5 checks=2J6 p=1 -> W2 [1,2) loads=[2,2,2] W=6 span=1 Cmax=2 LB=2/1 C/LB=1/1 checks=2J7 p=3 -> W0 [2,5) loads=[5,2,2] W=9 span=3 Cmax=5 LB=3/1 C/LB=5/3 checks=2FINAL Cmax=5 W=9 span=3 LB=3/1 C/LB=5/3 guarantee=5/3 comparisons=14
Complexity, Correctness, and Invariants
Feasibility and state invariants. After k accepted jobs, worker i's load equals the sum of durations assigned to it, total load equals work, and the cached makespan is the largest load. Every job is assigned exactly once; its start equals the chosen worker's previous load and its finish equals start plus duration. Appending these half-open intervals creates no overlap on a worker. Since no release times or precedence constraints exist, starting that worker's first job at zero is feasible. The maximum-duration counter is exactly the independent-job span. These statements follow by induction: initialization establishes zero sums, and each step changes exactly one load by the new duration while updating the corresponding counters.
Why the minimum scan is correct. After comparing worker i, the candidate is the smallest-ID worker with minimum load among workers 0 through i. The invariant holds initially for worker 0. A strictly smaller new load replaces the candidate; a tie retains the smaller already-scanned ID. At the end, the candidate minimizes the pair (load, ID) over all workers. The receipt records that worker's old load before changing it. Future jobs cannot affect an already returned assignment.
Competitive proof. Let C be the final list-scheduling makespan on a positive-work list. Choose a worker finishing at C, and let j be the last job appended to that worker. Write p for that job's duration and s for its start, so $C=s+p$. At its assignment the selected worker had minimum load s, hence every worker had load at least s. The total work assigned before j was therefore at least ms. It is at most W−p, because all other job durations are nonnegative, including any revealed after j. Consequently
$$s\le\frac{W-p}{m},\qquad C\le\frac Wm+\left(1-\frac1m\right)p.$$
Both $W/m\le\operatorname{OPT}$ and $p\le S\le\operatorname{OPT}$. Therefore
$$C\le\left(2-\frac1m\right)\operatorname{OPT}.$$
More strongly, because $W/m\le B$ and $p\le B$,
$$C\le\left(2-\frac1m\right)B\le\left(2-\frac1m\right)\operatorname{OPT}.$$
This also explains why the printed C/LB upper estimate never exceeds the guarantee for a positive-work prefix. The proof applies to every prefix considered as a completed input. All-zero inputs satisfy the inequality directly with C=OPT=0. For m=1 the factor is 1 and the only worker is optimal. No average-case distribution or amortization is needed for this quality guarantee.
Tightness for this algorithm. Give m workers $m(m-1)$ unit jobs, followed by one job of duration m. Least-load assignment balances the unit jobs at m−1 per worker, then assigns the last job to one of them. Thus
$$C=2m-1,\qquad W=m^2,\qquad S=m,\qquad B=m.$$
Offline puts the size-m job alone and packs m unit jobs on each of the other m−1 workers, attaining OPT=m. The ratio is exactly $2-1/m$. For m=1 the same construction is simply one unit job. This is a matching lower-bound example for list scheduling; it does not claim that every possible online policy has this exact optimal factor for every m.
Scheduler time and memory. The scan performs exactly $n(m-1)$ load comparisons. Each accepted job takes $\Theta(m)$ time including reading/scanning the loads and copying its receipt, in the best and worst cases; the same bound holds under any distribution because the scan does not stop early. Constructor cost is $\Theta(m)$, and processing n jobs takes $\Theta(m+nm)$ overall. For fixed m this is linear in n. An empty stream still pays initialization cost. Validation failures stop before assignment. No meaningful “average competitive factor” is specified without a distribution over lists; that is distinct from these runtime bounds.
The live controller uses $\Theta(m)$ memory. One current receipt and its load snapshot use $\Theta(m)$ additional memory. The shown driver prints each receipt immediately instead of retaining a history; its stored sample input adds O(n) space. Keeping all n load snapshots for later inspection would cost $\Theta(nm)$ storage, while retaining only each job's worker/start/finish costs O(n). Output itself contains $\Theta(nm)$ load values. Decimal formatting depends on integer digit length; exact reduced-fraction formatting uses a gcd, so the operation counts treat the bounded machine integers in the teaching API as constant-size. An arbitrary-precision unbounded analysis would account for those bit costs separately.
A heap version reduces the choice/update component to $O(1+\log m)$ worst-case time per job with $\Theta(m)$ live state, after O(m) heap construction. Copying and printing a full load vector after every job still takes $\Theta(m)$ values per receipt, so end-to-end full-trace output would mask that improvement. Round robin can choose a worker in constant time but does not use workload sizes and does not inherit the least-load proof. Competitive quality, decision overhead and processing makespan are three different measures.
The programs were tested against an independent worker-choice and interval reference, including exhaustive small lists, zeros, one worker, many ties, large durations, invalid inputs, capacity rejection and shared-prefix causality. Short workloads were also checked against exact offline partition enumeration. The tight construction was exercised for each supported worker count; receipt snapshots were checked after later updates, and all three programs' sample output was byte-identical.
| Approach | Decision/runtime cost without full snapshots | Guarantee for this model | Live scheduling state | Assumptions or limitation |
|---|---|---|---|---|
| Least-load scan, implemented | $\Theta(m)$ per job; $\Theta(m+nm)$ total | $2-1/m$ competitive | $\Theta(m)$ | Known current duration, identical workers, irrevocable assignment |
| Least-load binary heap | $O(1+\log m)$ update; O(m) construction | Same $2-1/m$ schedule when ties match | $\Theta(m)$ | Heap keyed by (load, ID); full load-vector output still costs $\Theta(m)$ |
| Round robin | $\Theta(1)$ per assignment | At most m on positive-work lists; can approach m | O(1) routing state; $\Theta(m)$ if loads are tracked | Distributes counts rather than work; no least-load bound |
| Offline LPT | $O(n\log(n+1)+n\log(m+1)+m)$ with heap | $4/3-1/(3m)$ approximation | $O(n+m)$ | Sorts the whole known list by decreasing duration; not online |
| Exact assignment enumeration | $O(nm^n)$ simple exhaustive evaluator | Optimal offline makespan | O(n+m) with depth-first enumeration | Useful only for tiny reference cases; sees the full list |
| Two random choices | Two probes plus update per job | No Graham guarantee follows from this rule alone | $\Theta(m)$ if loads are tracked | Familiar high-probability load results need specified unit jobs and randomness assumptions |
Every load equals the sum of its assigned jobs, and each new job is appended to a minimum-load worker. For the job finishing at the final makespan, that minimum start load is at most $(W-p)/m$; combining this with average-work and longest-job lower bounds proves the $2-1/m$ guarantee.
Variants, Pitfalls, and Optimizations
Online list versus real-time arrivals. If jobs have release dates $r_j$, a planned start must satisfy $s_j\ge r_j$; cumulative work alone no longer describes a worker's next available physical time without accounting for elapsed service and idle periods. Assigning a genuinely late job to an interval in the past is infeasible. An event simulator must advance a clock, process completions, maintain remaining work or availability, and define its allowed decisions. Reuse neither this simulator nor its proof unchanged for that setting.
Known durations versus unknown service. The classical list policy is clairvoyant only about the current job's size, not future jobs. Many actual services know only an estimate. Replacing sizes with estimates makes the least-load decision minimize predicted load; a deterministic guarantee for actual processing times then needs an error model. A few heavy tasks can make equal queue lengths highly unequal in work. The diagram below shows that even a causal count-based rule can violate the least-work bound.
Sorting, lookahead and migration change the contract. Longest-processing-time-first (LPT) sorts a fully known list in decreasing duration, then uses least-load assignment. Its classical offline approximation factor is $4/3-1/(3m)$, but the improved packing uses information an online list policy lacks. A bounded reordering buffer, future predictions or permission to move already assigned jobs are meaningful extensions; name their extra powers rather than calling the resulting experiment the same online algorithm. The code preserves the original reveal order and never repairs the sample after seeing the last job.
Worker heterogeneity and restrictions break the simple choice. With related machine speeds, duration depends on speed; with unrelated machines it may depend arbitrarily on the job/worker pair. Affinity, GPU memory, data locality, setup costs and eligibility constraints may forbid the current least-loaded worker. Choosing minimum projected finish time can be a sensible different heuristic, but the identical-machine proof does not establish the same competitive factor for those variants. Multi-resource load is a vector, so one scalar sum may not capture feasibility.
Dependencies require readiness. For an input DAG, the intrinsic span is the longest dependency path, not just the longest job. A worker cannot start a task before its predecessors complete. Greedy ready-task scheduling has related work/span analysis under stated assumptions, but appending arbitrary revealed tasks to a least-load queue here does not implement that algorithm. Do not use the displayed independent-job span for a workflow with chains, barriers or communication delays.
State consistency and ties matter. With a heap, remove an old worker entry before adding its updated key; leaving stale duplicates can choose an obsolete load. Compare (load, ID) lexicographically for the same deterministic schedule. A distributed router that reads stale loads concurrently may send several jobs to the same worker; the sequential proof assumes a consistent state and one committed assignment at a time. Snapshots should copy load values so later mutation cannot silently rewrite past evidence.
Change the objective only with a new analysis. Makespan minimizes the largest final load. It is not mean response time, total completion time, lateness, deadline misses, maximum queue length or fairness. Shortest-job-first, weighted fair queuing and deadline policies answer other objectives or constraints. Adding more workers reduces the average-work bound but cannot shrink an indivisible longest job; idle capacity may reflect an indivisibility bottleneck rather than a scheduling bug.
Use diagnostics honestly. B is only a lower bound. Label C/B as a ratio upper estimate until an exact optimum or a matching feasible packing establishes OPT. Empty and all-zero lists need direct zero-cost handling. A tight example proves the bound cannot be improved for this algorithm, not that every online algorithm is equally limited. Repeated favorable random tests do not replace a worst-case argument. Measure scheduler CPU overhead separately from the planned makespan; these programs simulate assignments rather than launching workers or measuring speedup.
Keep reveal order, duration knowledge, irrevocability, worker equivalence and the makespan objective explicit. Queue length is not total processing load, a lower bound is not necessarily OPT, and timed arrivals or future-aware sorting require different models and proofs.
Practice Problems and Real-World Use
Build a tiny offline oracle before comparing schedule quality. Enumerate each job's possible worker and minimize the maximum resulting sum. For a faster tiny reference, merge states that differ only by worker permutation; this preserves the optimum because workers are identical. The online trace must retain actual worker IDs to check deterministic ties. Use an independently written argmin rule for assignment checks, then compare every interval, prefix sum, span and makespan.
Include no jobs, all-zero jobs, m=1, more workers than jobs, repeated equal loads, a large job first, and a large job last. Verify $\sum_i L_i=W$, every interval length equals its duration, and successive intervals on the same worker meet without overlap. Feed two different futures after an identical prefix and require identical prefix receipts. Submit an invalid job after a valid prefix and ensure validation leaves the prior state untouched.
For each m, generate the unit-jobs-plus-large-job construction and prove its offline packing as well as measuring the online result. Then permute the same multiset and observe how order affects the schedule while offline OPT remains unchanged. Compare scan and heap policies with identical tie rules; their assignments should match exactly even though decision comparison counts differ. For an empirical random policy, specify the seed, size model, run count and what probability/expectation is being estimated.
The applications below are design exercises and possible abstraction points, not claims that the exact policy or its theorem applies to a named production system. Before adopting it, decide whether durations are known, workers are interchangeable, communication matters, assignments may migrate, and actual arrival times constrain starts. Instrument those assumptions along with the load metric.
Primary reading: MIT's approximation-algorithm notes develop the average-load/longest-job lower-bound proof for list scheduling. Leah Epstein's List Scheduling reference entry describes the online identical-machine model and its scheduling context. For the LPT comparison, Della Croce and Scatamacchia’s paper recalls its classical bound and studies refinements. The driver/controller split, validation limits, exact fraction displays, sample trace and test harness here are specific to this lesson.
| Practice task or possible use | Idea tested | Difficulty |
|---|---|---|
| Deterministic online assignment simulator | Minimum-load scan, ties, intervals and per-prefix invariants | Beginner |
| Tight-bound generator for each m | Unit-job prefix, last large job and matching offline packing | Intermediate |
| Scan-versus-heap dispatcher | Same schedule with different decision overhead and heap invariants | Intermediate |
| Tiny exact optimum laboratory | Enumerate assignments; distinguish B, OPT and C | Intermediate |
| Known-duration batch-worker pool | Explain when identical-worker, no-release abstraction fits | Intermediate |
| Estimated-duration request routing | Measure prediction error and separate counts from workload | Advanced |
| Timed-arrival event simulator | Release feasibility, completions, remaining work and a revised model | Advanced |
| Dependency-aware build or workflow scheduler | Ready tasks, intrinsic DAG span and communication constraints | Advanced |