Disk Scheduling Algorithms

Imagine a database server handling a hundred pending read requests. These requests point to blocks scattered randomly across a spinning disk. Serve them in the order they arrived, and the disk arm might whip from the innermost track to the outermost. It could do this dozens of times, wasting milliseconds on every trip. Serve them cleverly instead, and the same requests complete in a fraction of the time. This happens because the arm sweeps smoothly across the surface, picking up nearby work along the way. This is the disk scheduling problem: given a queue of pending I/O requests, each naming a cylinder to visit, decide the order of service to minimize the mechanical cost of moving the read/write head. The stakes are real. On a mechanical hard disk, seek time (moving the arm to the right cylinder) dominates the cost of an I/O operation. It often takes several milliseconds — an eternity compared to the microsecond-scale cost of transferring data once the head is positioned. A busy multi-user system, database, or video server can queue up dozens of requests within a single rotation. The order in which the operating system's disk scheduler dispatches them can change total service time by an order of magnitude. Even though solid-state drives have displaced spinning disks in many modern deployments, the queueing and scheduling ideas developed for disks persist. They live on in I/O schedulers for SSDs, in network-attached storage, and in RAID controllers. Conceptually, they apply to any system that must sequence physical or logical head movements over an ordered address space. The big picture is that disk scheduling sits between the file system and the disk controller. The file system issues logical read/write requests. The disk controller understands physical geometry: cylinders, tracks, and sectors. The operating system's I/O subsystem maintains a queue of pending requests, each tagged with a target cylinder. A scheduling policy decides which request to service next. This article covers the five classical algorithms every operating systems course teaches: First-Come-First-Served (FCFS), Shortest-Seek-Time-First (SSTF), SCAN, C-SCAN, and LOOK. Together they illustrate a recurring systems tradeoff — between fairness, throughput, and predictability. This tradeoff reappears throughout scheduling in general. By the end you will be able to trace each algorithm on a concrete request sequence. You will compute total head movement by hand, and reason about starvation and variance in wait times. You will understand why real disk controllers combine these ideas with rotational latency optimization and request merging. This material builds directly on Disk Structure and Geometry and Interrupts and DMA. It sets up the deeper story of File-System Implementation and Layout, where the layout of data on disk is chosen partly to make scheduling easier.

Key takeaways
  • Disk scheduling orders pending I/O requests to minimize total seek time, the dominant cost of a mechanical disk access.
  • FCFS is simple and fair but ignores geometry, producing long average seek distances under bursty, scattered request patterns.
  • SSTF greedily picks the nearest request each time; it minimizes local seek cost but can starve far-away requests indefinitely.
  • SCAN and C-SCAN sweep the arm like an elevator, bounding worst-case wait time and eliminating starvation.
  • LOOK and C-LOOK refine SCAN/C-SCAN by reversing at the last request rather than the disk's physical edge, saving wasted travel.
  • No algorithm is universally best — the right choice trades off throughput, fairness, and variance depending on workload.
The core insight

Disk scheduling reorders a queue of pending requests so the read/write head travels the least total distance, trading fairness for throughput in a controlled, boundable way.

The idea

A disk request is characterized primarily by the cylinder number it targets, since moving the arm between cylinders (the seek) costs far more time than waiting for the platter to rotate into position (rotational latency) or transferring data once positioned. Given a queue of pending requests with cylinder numbers c1,c2,…,cnc_1, c_2, \dots, c_n and a current head position hh, a disk scheduling algorithm picks an order of service π\pi to reduce some cost function — typically total head movement, ∑i∣cπ(i)−cπ(i−1)∣\sum_i |c_{\pi(i)} - c_{\pi(i-1)}|, or the maximum/average waiting time across requests. Two families of policies emerge. Order-preserving policies like FCFS make no attempt to reorder; they are simple, predictable, and starvation-free by construction, but they let the arm thrash back and forth across the disk if requests arrive in geometrically scattered order. Greedy and sweep policies — SSTF, SCAN, C-SCAN, and LOOK — actively reorder requests based on cylinder position. Greedy policies (SSTF) minimize the immediate next seek distance, which can produce a good average but risks starvation: a request far from the current cluster of activity may wait indefinitely if closer requests keep arriving. Sweep policies impose a global direction of travel — the arm behaves like an elevator, visiting cylinders in one direction until it must reverse — which bounds the worst-case wait for any request to at most one full sweep of the disk. The key terms to fix precisely: seek time is the time to move the arm to the target cylinder; rotational latency is the time waiting for the target sector to rotate under the head; cylinder is the concentric set of tracks at the same radius across all platter surfaces, reachable by a single arm position. Disk scheduling algorithms operate purely on the sequence of cylinder numbers in the request queue — they do not, in their classical form, account for rotational latency or track layout, which is why real controllers layer additional heuristics (such as SPTF, shortest-positioning-time-first) on top of the ideas taught here.

How it works

Every algorithm below takes the same inputs — a set of pending request cylinders, the current head position, the total number of cylinders on the disk, and (where relevant) the current direction of travel — and produces an ordering in which to service them. We trace all five over a common example so the differences are visible mechanically, not just in prose.

FCFS (First-Come-First-Served). Service requests strictly in arrival order. No reordering, no knowledge of geometry needed. ``` FCFS(requests, head): order = [] current = head total_movement = 0 for r in requests: # requests already in arrival order total_movement += |r - current| order.append(r) current = r return order, total_movement ```

SSTF (Shortest-Seek-Time-First). At each step, service whichever pending request is closest to the current head position, breaking ties arbitrarily (commonly by direction or FCFS order). ``` SSTF(requests, head): pending = copy(requests) # multiset of cylinder numbers order = [] current = head total_movement = 0 while pending is not empty: r = argmin over x in pending of |x - current| total_movement += |r - current| order.append(r) remove r from pending current = r return order, total_movement ``` A priority structure (e.g., a balanced BST or two sorted lists split around the head) supports the repeated nearest-neighbor query in O(log⁡n)O(\log n) per step rather than a linear scan.

SCAN (the "elevator" algorithm). The head sweeps in one direction, servicing every pending request it passes, until it reaches the end of the disk (cylinder 0 or the maximum cylinder), then reverses direction and sweeps back. ``` SCAN(requests, head, direction, max_cylinder): left = sorted(r for r in requests if r < head) right = sorted(r for r in requests if r >= head) order = [] if direction == "up": order += right # ascending, head to max order += [max_cylinder] # sweep to the physical end order += reversed(left) # then descend else: # direction == "down" order += reversed(left) # descending, head to 0 order += [0] order += right # then ascend return order ```

C-SCAN (Circular SCAN). Like SCAN, but the head services requests only while moving in one direction; upon reaching the end, it jumps immediately back to the opposite end without servicing on the return trip, then resumes sweeping the same direction. This makes wait times far more uniform, since the disk is treated as a circular list of cylinders. ``` C-SCAN(requests, head, max_cylinder): right = sorted(r for r in requests if r >= head) left = sorted(r for r in requests if r < head) order = [] order += right order += [max_cylinder] # sweep to the end order += [0] # jump back to the start (no service) order += left # resume sweeping upward return order ```

LOOK and C-LOOK. Identical to SCAN and C-SCAN, respectively, except the arm reverses (or jumps) at the last request in the current direction rather than traveling all the way to the physical edge of the disk. This avoids wasted travel when no requests lie near the edges. ``` LOOK(requests, head, direction): left = sorted(r for r in requests if r < head) right = sorted(r for r in requests if r >= head) order = [] if direction == "up": order += right order += reversed(left) else: order += reversed(left) order += right return order C_LOOK(requests, head): right = sorted(r for r in requests if r >= head) left = sorted(r for r in requests if r < head) order = right + left # jump from max(right) directly to min(left) return order ```

Figure
SCAN vs. C-SCAN head movement across cylinders 0–199, requests at 20, 55, 90, 130, 170, head starting at 100 moving toward higher cylinders
SCAN (top) vs. C-SCAN (bottom), head starts at 100, moving up 0 199 20 55 90 130 170 head 100 to edge 199 to edge 0 0 199 dashed = fast return jump (no service on return trip) SCAN total: services requests both directions across a single reversal at each physical edge. C-SCAN total: services only while sweeping up; jump back is uniform, giving even wait times.

Complexity & correctness

Time complexity. Let nn be the number of pending requests.

  • FCFS requires a single pass over the queue: O(n)O(n) to compute the order and total movement, since no sorting or searching is needed.
  • SSTF, implemented naively, scans all pending requests at each of the nn steps to find the nearest one, giving O(n2)O(n^2). Using a balanced search structure (e.g., a sorted array or balanced BST of pending cylinders, allowing predecessor/successor queries), each step costs O(log⁡n)O(\log n), giving O(nlog⁡n)O(n \log n) total.
  • SCAN, C-SCAN, LOOK, and C-LOOK all begin by partitioning requests into those below and at/above the head, then sorting each partition: O(nlog⁡n)O(n \log n) for the sort, and O(n)O(n) to emit the final order — so O(nlog⁡n)O(n \log n) overall. If the request queue is maintained as an always-sorted structure (common in practice, since requests can be inserted into a sorted set as they arrive), the per-request cost drops to O(log⁡n)O(\log n) for insertion, and generating an order is O(n)O(n).
  • Space complexity for all algorithms is O(n)O(n), to hold the request queue and the output order.

Correctness argument for SCAN/C-SCAN/LOOK (no starvation). Define a sweep as one traversal of the head from one extreme (or from its farthest pending request, for LOOK variants) to the other. The invariant maintained by SCAN is: at the start of each sweep, every request present in the queue is served exactly once before the sweep ends, in monotonic cylinder order relative to the sweep direction. This holds because the algorithm partitions all currently pending requests into "ahead of the head" and "behind the head" relative to the current direction, and visits the "ahead" partition in full, sorted order, before reversing. Consequently, any request present when a sweep begins is serviced within that sweep or the very next one — bounding the worst-case wait to at most two sweep lengths (roughly 2×2 \times the number of cylinders in the worst case, independent of how many other requests arrive). This is why SCAN and C-SCAN are described as starvation-free: no request can be perpetually skipped, unlike SSTF.

Why SSTF can starve. SSTF always chooses the globally nearest pending request. Consider a steady stream of new requests arriving near the current head position while a single old request sits far away (say, at cylinder 0 while the head oscillates near cylinder 190). Every greedy choice prefers the nearby newcomers, since ∣nearby−head∣≪∣0−head∣|nearby - head| \ll |0 - head|, so the distant request's wait time is unbounded as long as nearby arrivals continue — an unbounded delay is possible in principle, which is the formal meaning of starvation here.

Why C-SCAN gives more uniform response time than SCAN. SCAN's cost per direction is asymmetric: a request that was just missed on an upward sweep must wait for the full downward-then-upward return, while a request just serviced waits almost a full cycle before being revisited. C-SCAN removes this asymmetry by always servicing in the same direction and treating the cylinder space as circular (wrapping from the max cylinder back to 0 with no intervening service). The variance in wait time across requests is provably smaller for C-SCAN because every request's maximum wait is bounded by one full circular sweep, rather than by the sum of two unequal partial sweeps.

Tradeoffs, honestly stated. FCFS minimizes implementation complexity and variance-free fairness in arrival order but can have the worst average seek distance under adversarial or bursty arrival patterns. SSTF minimizes average seek distance for a fixed static request set but is not starvation-free and its optimal reordering (the true minimum-total-movement tour) is actually an instance of the NP-hard Traveling Salesman Problem on a line — SSTF is a greedy heuristic, not an exact optimizer, though on a line metric greedy nearest-neighbor is known to perform reasonably close to optimal in practice. SCAN and LOOK trade a small amount of average-case efficiency for a hard bound on worst-case wait. C-SCAN sacrifices some total movement (the "free" return trip in SCAN is replaced by a full unserviced jump in C-SCAN) in exchange for much more uniform per-request response times, which matters more for real-time and interactive workloads than raw throughput does.

Worked example

Consider a disk with cylinders numbered 0 to 199, head currently at cylinder 53, and the pending request queue, in arrival order:

98, 183, 37, 122, 14, 124, 65, 6798,\ 183,\ 37,\ 122,\ 14,\ 124,\ 65,\ 67

Assume the head's most recent direction of travel was toward increasing cylinder numbers (relevant for SCAN/LOOK variants).

AlgorithmService order (from head 53)Total head movement (cylinders)
FCFS98, 183, 37, 122, 14, 124, 65, 6745+85+146+85+108+110+59+2 = 640
SSTF65, 67, 37, 14, 98, 122, 124, 18312+2+30+23+84+24+2+59 = 236
SCAN (up, edge=199)65, 67, 98, 122, 124, 183, 199, 37, 14, 012+2+31+24+2+59+16+162+23+14 = 345
LOOK (up)65, 67, 98, 122, 124, 183, 37, 1412+2+31+24+2+59+146+23 = 299
C-SCAN (up, edge=199)65, 67, 98, 122, 124, 183, 199, 0, 14, 3712+2+31+24+2+59+16+199+14+23 = 382
C-LOOK (up)65, 67, 98, 122, 124, 183, 14, 3712+2+31+24+2+59+169+23 = 322

Let's verify FCFS by hand as a sanity check: from 53 to 98 is 45; 98 to 183 is 85; 183 to 37 is 146; 37 to 122 is 85; 122 to 14 is 108; 14 to 124 is 110; 124 to 65 is 59; 65 to 67 is 2. Sum: 45+85+146+85+108+110+59+2=64045+85+146+85+108+110+59+2 = 640. This confirms FCFS produces by far the worst total movement here, since arrival order zig-zags across nearly the entire disk multiple times. SSTF's superiority in total movement is visible immediately (236 vs. 640), but notice the service order: 14, the smallest cylinder and second request to arrive, is serviced fifth, after several closer requests are picked up first. If new nearby requests kept arriving near cylinders 60–130, cylinder 14 (and 183) could wait far longer under SSTF than under any SCAN variant — this is the starvation risk made concrete. Comparing LOOK to SCAN: LOOK skips the "dead" trips to the physical edges (199 and 0) that no pending request actually needed, saving 345−299=46345 - 299 = 46 cylinders of movement while producing the same relative service order. Comparing C-LOOK to C-SCAN similarly saves the wasted trip to 199, at the cost of the long jump back from 124 down to 14 (a distance of 110 cylinders that services nothing) — but that entire circular-wrap distance is still cheaper than the two dead-edge trips C-SCAN pays.

Common pitfalls & traps

PitfallWhy it happensFix
Forgetting the current direction of travel in SCAN/LOOKStudents treat the head as starting "fresh" each time, ignoring which way it was already movingTrack direction as explicit scheduler state, updated after each service decision
Confusing LOOK with SCANBoth look similar in pseudocode; the difference (reversing at last request vs. physical edge) is easy to skipAlways ask: does the arm travel to cylinder 0/max even with no request there?
Treating SSTF as globally optimalSSTF looks "smart" and often outperforms FCFS badly, tempting the false belief it's optimalRemember SSTF is a greedy heuristic to a line-metric TSP; recompute the true minimum-movement tour for small cases to see the gap
Ignoring starvation in SSTF under continuous arrivalsStatic examples with a fixed queue hide the starvation effect entirelySimulate with a continuous arrival stream biased toward one region, and watch a distant request's wait time grow
Off-by-one on disk edges in C-SCANForgetting that the return jump is unserviced, or double-counting the max cylinder as a requestExplicitly separate "requests" from "the physical edge cylinder used only to compute distance"
Assuming disk scheduling models apply directly to SSDsMechanical seek-time reasoning was designed for spinning platters, not flashRecognize modern SSD schedulers optimize for parallelism and wear-leveling, not seek distance

Pioneers & history

Disk scheduling emerged directly from the economics of early magnetic disk storage: mechanical seek time was the dominant cost of an I/O operation for decades, so operating systems researchers and industry engineers invested heavily in scheduling heuristics throughout the 1960s and 1970s, as multiprogrammed and time-sharing systems made disk contention a first-order performance concern.

Origins · 1 minEdsger W. Dijkstra and how this was discovered
EW
Edsger W. Dijkstra
1930–2002 · Formalized structured approaches to systems and scheduling reasoning that underlie disk scheduling correctness arguments

Dijkstra's broader work on cooperating sequential processes, mutual exclusion, and structured resource allocation gave later systems researchers the conceptual vocabulary — invariants, fairness, bounded waiting — used to reason rigorously about scheduling policies including disk arm scheduling.

History
1960
s — early time-sharing systems expose disk seek time as a major throughput bottleneck, motivating scheduling research.
1970
s — FCFS, SSTF, SCAN, and C-SCAN are formalized and compared in operating systems textbooks and industry disk-controller designs.
1980
s — LOOK and C-LOOK variants are adopted in commercial Unix kernels to avoid wasted edge-to-edge travel.
1990
s — anticipatory and deadline-based I/O schedulers (in Linux and other kernels) combine SCAN-like sweeps with fairness and latency guarantees for interactive workloads.
2000
s–2010s — solid-state drives shift scheduling priorities away from seek time toward parallel channel utilization and wear-leveling, though sweep-based fairness ideas persist in modern I/O schedulers.

Where this matters

Every general-purpose operating system kernel ships a disk (or block-layer) I/O scheduler descended from these ideas. Linux's historical schedulers — including the deadline scheduler and CFQ (Completely Fair Queuing) — build on SCAN-like sweeping combined with deadline guarantees to prevent starvation while preserving throughput; even `mq-deadline` and `kyber`, used with modern multi-queue block layers, retain sweep-inspired sequencing for spinning media. Database systems and RAID controllers historically tuned their own request reordering layers on top of the OS scheduler to batch and sort I/O for mechanical drives. Real-time and embedded systems favored SCAN/C-SCAN variants specifically because of the provable bound on worst-case wait time, which matters when a control-loop deadline cannot be missed. Even as solid-state drives (with no moving arm and effectively uniform access latency across addresses) have made seek-time minimization less relevant for consumer storage, the conceptual toolkit — greedy heuristics versus bounded sweeps, throughput versus fairness, starvation-avoidance guarantees — reappears throughout systems design: in network packet schedulers, in CPU process schedulers, and in any resource-allocation policy where request reordering trades efficiency for predictability. Disk scheduling is also a natural bridge to File-System Implementation and Layout, where file systems place related blocks physically close together specifically to make the scheduler's job easier and reduce the seek distances it must manage in the first place.

Ask the tutor: How would you modify SCAN to bound the maximum wait time for any single request to a fixed number of I/O operations, regardless of arrival pattern?

Why does disk scheduling matter less for SSDs?

Solid-state drives have no mechanical arm, so there is no seek time in the mechanical sense — any address can be accessed in roughly uniform time, limited mainly by flash channel and controller parallelism. The core motivation behind SCAN, C-SCAN, and SSTF — minimizing physical arm travel — mostly disappears. Modern SSD-aware I/O schedulers instead focus on maximizing parallel channel utilization, batching writes to reduce write amplification, and respecting wear-leveling constraints. That said, fairness and starvation-avoidance concepts from classical disk scheduling persist conceptually in modern multi-queue block-layer schedulers, since request reordering for throughput versus latency tradeoffs is still relevant even without mechanical seeks.

Is SSTF ever the "correct" choice for total minimum movement?

Not exactly — SSTF is a greedy heuristic, not an exact solver. The true problem of finding the ordering that minimizes total head movement is equivalent to the Traveling Salesman Problem restricted to points on a line, which can actually be solved efficiently in this special one-dimensional case, but SSTF's greedy nearest-neighbor rule does not always find that optimal ordering; it can get trapped making a series of small optimal-looking moves that leave a large distant jump for later. In practice, SSTF performs well because disk workloads rarely present adversarial worst cases, but it is a heuristic with no worst-case optimality guarantee.

Why do textbooks emphasize total head movement instead of actual wall-clock seek time?

Total head movement (in cylinders) is used as a clean, hardware-independent proxy for seek time because the actual physical relationship between cylinder distance and time is nonlinear and disk-specific — it involves acceleration, deceleration, and settling time of the arm. Comparing algorithms by cylinder-distance sums isolates the scheduling policy's contribution to performance from the disk hardware's physical characteristics, letting the same analysis apply across different drive models. Real systems do ultimately care about time, and controllers may incorporate manufacturer-specific seek-time curves, but the cylinder-distance abstraction is the standard teaching and comparison tool.

How does C-SCAN's fairness improvement actually get measured?

Fairness here is usually measured by the variance (or the maximum) of individual request wait times across the workload, not by the total or average head movement. SCAN produces asymmetric wait times: a request just missed near one edge might wait almost two full sweeps, while a request near the middle waits about one sweep. C-SCAN, because it always services in a single direction and treats the disk cylinders as a circular structure, gives every request a wait time bounded by roughly one full circular pass, which is more uniform even though the total distance traveled (including the free unserviced return jump) can be higher than SCAN's.

Can a disk scheduler combine algorithms, and does that happen in practice?

Yes. Real kernel I/O schedulers frequently layer ideas: a base SCAN-like sweep for throughput and starvation-avoidance, combined with per-request deadlines (as in Linux's deadline scheduler) so that no single request waits longer than a configured maximum regardless of the sweep's progress. Some schedulers additionally merge adjacent requests targeting nearby sectors into a single larger I/O operation before scheduling, reducing the effective number of seeks needed. This hybrid approach captures SCAN's fairness bound while adding an explicit, tunable latency guarantee that pure SCAN does not offer on its own.

Does the choice of disk scheduling algorithm affect correctness, or only performance?

Purely performance — none of these algorithms change what data is eventually read or written, only the order and timing in which pending requests are serviced. Correctness of the underlying file system and I/O subsystem (data consistency, no lost writes, proper synchronization) is guaranteed independently by mechanisms like request queues, locking, and completion callbacks tied to Interrupts and DMA. Disk scheduling is entirely an optimization layer: a poorly chosen algorithm can make a system slow or produce unfair latency distributions, but it cannot, by itself, corrupt data or violate program correctness.

Why is rotational latency ignored in the classical algorithms described here?

The classical five algorithms were designed as pure cylinder-ordering heuristics, focusing only on the dominant cost component — seek time — because historically it was an order of magnitude larger than rotational latency for typical workloads and drive geometries. Real disk controllers, especially those exposing detailed geometry to the OS, can perform shortest-positioning-time-first (SPTF) scheduling, which folds in rotational latency by knowing sector angular positions, not just cylinder numbers. This adds real value on drives where seek and rotational costs are comparable, but it requires far more detailed hardware knowledge than the classical algorithms assume, which is why the simpler cylinder-only model remains the standard teaching baseline.

Before this

Where this leads