The Story That Explains SJF
That is why supermarkets invented the Express Lane — 10 items or fewer. It is not fair in the FIFO sense — it lets a later, shorter customer jump ahead. But it is mathematically optimal: total waiting time collapses when short jobs go first.
That is Shortest Job First (SJF). The CPU is the till. The queue re-orders itself so the process with the smallest burst time always runs next.
Galvin proves that SJF gives the provably minimum average waiting time for any set of processes — no other algorithm can do better. This makes SJF the theoretical gold standard against which every other scheduler is measured.
Running short jobs first minimises the sum of waiting times. Every long-job-first ordering can be improved by swapping a long-then-short pair for a short-then-long pair. SJF is the endpoint of that optimisation.
The Two Flavours of SJF
SJF Algorithm — Six Steps
SJF requires knowing burst time before running the process — which is impossible in practice. Real systems predict the next burst using exponential averaging of past bursts. We'll cover this in Section 09.
The Metrics — Same As FCFS
Numerical 1 — Basic SJF (All Arrive at t=0)
Problem: Four processes arrive at time 0. Compute all metrics under non-preemptive SJF.
| Process | Arrival Time (AT) | Burst Time (BT) |
|---|---|---|
| P1 | 0 | 7 |
| P2 | 0 | 4 |
| P3 | 0 | 1 |
| P4 | 0 | 4 |
Sort By Burst Time First
Animated Step-Wise Gantt Chart
Notice how the shortest job (P3, 1 ms) runs first — even though P1 was written first in the input.
Solution Table
| Process | AT | BT | Start | CT | TAT | WT |
|---|---|---|---|---|---|---|
| P3 | 0 | 1 | 0 | 1 | 1 | 0 |
| P2 | 0 | 4 | 1 | 5 | 5 | 1 |
| P4 | 0 | 4 | 5 | 9 | 9 | 5 |
| P1 | 0 | 7 | 9 | 16 | 16 | 9 |
| Averages | 7.75 | 3.75 | ||||
Numerical 2 — Non-Preemptive SJF With Different Arrivals
Problem: Processes arrive at different times. Apply non-preemptive SJF.
| Process | AT | BT |
|---|---|---|
| P1 | 0 | 6 |
| P2 | 1 | 8 |
| P3 | 2 | 7 |
| P4 | 3 | 3 |
Step-by-Step Timeline
Animated Gantt Chart
P1 runs first because it is the only one ready at t=0. After that, SJF picks by burst — P4 (3) before P3 (7) before P2 (8).
| Process | AT | BT | Start | CT | TAT | WT |
|---|---|---|---|---|---|---|
| P1 | 0 | 6 | 0 | 6 | 6 | 0 |
| P4 | 3 | 3 | 6 | 9 | 6 | 3 |
| P3 | 2 | 7 | 9 | 16 | 14 | 7 |
| P2 | 1 | 8 | 16 | 24 | 23 | 15 |
| Averages | 12.25 | 6.25 | ||||
Numerical 3 — Preemptive SJF (SRTF)
Same data as Numerical 2, but now the scheduler is Shortest Remaining Time First (SRTF). Every arrival is a scheduling event.
| Process | AT | BT |
|---|---|---|
| P1 | 0 | 6 |
| P2 | 1 | 8 |
| P3 | 2 | 7 |
| P4 | 3 | 3 |
Second-By-Second Reasoning
In this specific numerical, P1's remaining time is always ≤ the newcomer's burst, so no preemption fires. The schedule matches non-preemptive SJF exactly. Let's build a numerical where preemption genuinely triggers.
Numerical 3B — Preemption That Actually Fires
| Process | AT | BT |
|---|---|---|
| P1 | 0 | 8 |
| P2 | 1 | 4 |
| P3 | 2 | 9 |
| P4 | 3 | 5 |
Animated Preemptive SJF Gantt
P1 gets preempted just 1 ms after starting, because P2's whole burst (4 ms) is shorter than P1's remaining (7 ms). Watch P1 return later to finish its remaining 7 ms.
SRTF Solution Table
| Process | AT | BT | CT | TAT | WT |
|---|---|---|---|---|---|
| P1 | 0 | 8 | 17 | 17 | 9 |
| P2 | 1 | 4 | 5 | 4 | 0 |
| P3 | 2 | 9 | 26 | 24 | 15 |
| P4 | 3 | 5 | 10 | 7 | 2 |
| Averages | 13.00 | 6.50 | — | ||
Numerical 4 — Starvation Demonstration
SJF's dark side: if short jobs keep arriving, long jobs may never run. This is called starvation.
| Process | AT | BT |
|---|---|---|
| P1 (long) | 0 | 20 |
| P2 | 1 | 2 |
| P3 | 3 | 2 |
| P4 | 5 | 2 |
| P5 | 7 | 2 |
| P6 | 9 | 2 |
Every short newcomer keeps preempting P1. In a real system with continuous short arrivals, P1 would starve indefinitely.
Real schedulers combat SJF starvation with aging: gradually raise a waiting process's priority (or shrink its effective burst) the longer it waits. Eventually every process becomes "shortest enough" and gets served.
Predicting the Next CPU Burst
Since we can't know a process's burst time in advance, we estimate it from the process's past bursts using an exponential-averaging formula.
# Simple exponential burst predictor
class BurstPredictor:
def __init__(self, alpha=0.5, initial_guess=10):
self.alpha = alpha
self.tau = initial_guess
def predict(self) -> float:
return self.tau
def update(self, actual_burst: float) -> None:
self.tau = self.alpha * actual_burst + (1 - self.alpha) * self.tau
# Example run
p = BurstPredictor(alpha=0.5, initial_guess=10)
history = [6, 4, 6, 4, 13, 13, 13]
for b in history:
print(f"predicted={p.predict():5.2f} actual={b}")
p.update(b)
Full Python Implementation
from dataclasses import dataclass
from typing import List
@dataclass
class Process:
pid: str
arrival: int
burst: int
remaining: int = 0
start: int = -1
completion: int = -1
# --- NON-PREEMPTIVE SJF -------------------------------------
def sjf(procs: List[Process]) -> List[Process]:
procs = [Process(p.pid, p.arrival, p.burst) for p in procs]
finished = []
clock = 0
while procs:
ready = [p for p in procs if p.arrival <= clock]
if not ready:
clock = min(p.arrival for p in procs)
continue
# Pick shortest burst, break tie by arrival
next_p = min(ready, key=lambda p: (p.burst, p.arrival))
next_p.start = clock
next_p.completion = clock + next_p.burst
clock = next_p.completion
procs.remove(next_p)
finished.append(next_p)
return finished
# --- PREEMPTIVE SJF (SRTF) ----------------------------------
def srtf(procs: List[Process]) -> List[Process]:
procs = [Process(p.pid, p.arrival, p.burst, remaining=p.burst)
for p in procs]
total_burst = sum(p.burst for p in procs)
clock = 0
while any(p.remaining > 0 for p in procs):
ready = [p for p in procs
if p.arrival <= clock and p.remaining > 0]
if not ready:
clock += 1
continue
current = min(ready, key=lambda p: p.remaining)
if current.start == -1:
current.start = clock
current.remaining -= 1
clock += 1
if current.remaining == 0:
current.completion = clock
return procs
def report(name, procs):
print(f"\n=== {name} ===")
tat = wt = 0
for p in procs:
t = p.completion - p.arrival
w = t - p.burst
tat += t; wt += w
print(f"{p.pid} start={p.start:2} CT={p.completion:2} TAT={t:2} WT={w:2}")
n = len(procs)
print(f"Avg TAT={tat/n:.2f} Avg WT={wt/n:.2f}")
# --- Run both on the SRTF numerical -------------------------
workload = [
Process("P1", 0, 8),
Process("P2", 1, 4),
Process("P3", 2, 9),
Process("P4", 3, 5),
]
report("Non-Preemptive SJF", sjf(workload))
report("Preemptive SJF (SRTF)", srtf(workload))
SJF vs FCFS Side-By-Side
| Property | FCFS | SJF (Non-Preemptive) | SRTF (Preemptive) |
|---|---|---|---|
| Selection criterion | Arrival order | Shortest burst among ready | Shortest remaining time |
| Preemption | No | No | Yes |
| Avg waiting time | Often poor | Optimal (given arrivals) | Globally optimal |
| Starvation risk | None | Yes | Yes |
| Overhead | Very low | Low (one sort per dispatch) | High (check on every arrival) |
| Requires burst prediction | No | Yes | Yes |
| Convoy effect | Severe | Solves it | Solves it |
Advantages & Disadvantages
| Provably minimises average waiting time |
| Fixes the convoy effect that plagues FCFS |
| Great for batch systems with predictable burst lengths |
| SRTF offers even better response for interactive short jobs |
| Requires knowing burst time in advance — impossible in practice |
| Starvation of long jobs if short ones keep arriving |
| Prediction errors hurt performance significantly |
| SRTF adds overhead — decision at every arrival |