Operating Systems 📂 Scheduling Algorithm · 2 of 5 41 min read

SJF Scheduling — Step-Wise Animated Numericals

Master Shortest Job First CPU scheduling with four fully-worked numericals covering both non-preemptive SJF and preemptive SRTF. Watch each Gantt chart get built block-by-block with a live narrator explaining every dispatch and preemption. Covers the algorithm, all four metrics, a preemption trace with a "PREEMPT!" flash marker, the starvation problem, exponential-average burst prediction, and full Python implementations of both variants.

Section 01

The Story That Explains SJF

The Supermarket Express Lane
Picture a supermarket with two customers at the till. The first has a trolley overflowing with 200 items. The second holds a single loaf of bread. If we serve strictly by arrival order, the bread customer waits 15 minutes for a 20-second transaction.

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.

🏆
The Core Insight

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.


Section 02

The Two Flavours of SJF

🔒
Non-Preemptive SJF
"Pure SJF"
Whenever the CPU becomes free, pick the ready process with the smallest burst time. Once dispatched, it runs to completion. A shorter arrival later does not interrupt it.
Preemptive SJF (SRTF)
Shortest Remaining Time First
At every arrival, compare the new process's burst with the running process's remaining time. If the newcomer is shorter, preempt immediately.
📈
Optimality
Minimum avg WT
Both variants minimise average waiting time. Preemptive SRTF wins on mixed workloads because it can react to a new short job instantly.

Section 03

SJF Algorithm — Six Steps

🔄 Non-Preemptive SJF — The Rules
Step 1
Advance the clock. Determine the set of processes that have arrived by the current time.
Step 2
Among ready processes, find the one with the smallest burst time. Tie-break by arrival order.
Step 3
Dispatch that process. It runs for its full burst time — no preemption.
Step 4
Advance the clock by the burst. Record completion time.
Step 5
If no process has arrived yet, the CPU sits idle until the next arrival.
Step 6
Repeat from Step 1 until every process has completed.
⚠️
The Prediction Problem

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.


Section 04

The Metrics — Same As FCFS

Completion Time
CT = start + burst
The instant a process finishes on the CPU.
Turnaround Time
TAT = CT − AT
Total time from arrival to completion.
Waiting Time
WT = TAT − BT
Time sitting in the ready queue — SJF minimises this.
Response Time
RT = start − AT
For non-preemptive SJF, RT == WT. For SRTF they differ.

Section 05

Numerical 1 — Basic SJF (All Arrive at t=0)

Problem: Four processes arrive at time 0. Compute all metrics under non-preemptive SJF.

ProcessArrival Time (AT)Burst Time (BT)
P107
P204
P301
P404

Sort By Burst Time First

01
t=0 — Rank ready processes by burst
Ready: {P1:7, P2:4, P3:1, P4:4}. Shortest = P3 (1 ms). Dispatch P3.
02
t=1 — P3 done. Re-rank
Remaining: {P1:7, P2:4, P4:4}. Shortest tie between P2 & P4. Break tie by arrival order → P2.
03
t=5 — P2 done. Dispatch P4
Remaining: {P1:7, P4:4}. P4 shorter → dispatch P4.
04
t=9 — Only P1 left. Runs to end
Dispatch P1. Completes at t=16.

Animated Step-Wise Gantt Chart

Building the Gantt Chart — Shortest First
SJF Order: P3 → P2 → P4 → P1 P3 P2 P4 P1 (7 ms) 0 1 5 9 16 Step 1: All arrive at t=0. Pick smallest burst → P3 (1 ms). CT(P3)=1, WT(P3)=0. Step 2: Ready = {P1:7, P2:4, P4:4}. Tie 4/4 → earlier arrival (P2). CT(P2)=5, WT(P2)=1. Step 3: Ready = {P1:7, P4:4}. Shortest = P4. CT(P4)=9, WT(P4)=5. Step 4: Only P1 remains. Runs 9→16. CT(P1)=16, WT(P1)=9. Avg WT = (9 + 1 + 0 + 5)/4 = 3.75 ms Avg TAT = (16 + 5 + 1 + 9)/4 = 7.75 ms

Notice how the shortest job (P3, 1 ms) runs first — even though P1 was written first in the input.

Solution Table

ProcessATBT StartCT TATWT
P3010110
P2041551
P4045995
P107916169
Averages 7.75 3.75
FCFS COMPARISON (SAME DATA)
FCFS order: P1 → P2 → P3 → P4 FCFS Avg WT = (0 + 7 + 11 + 12) / 4 = 7.50 ms SJF Avg WT = (0 + 1 + 5 + 9) / 4 = 3.75 ms SJF wins by 50%. Same processes, same total burst — different order.

Section 06

Numerical 2 — Non-Preemptive SJF With Different Arrivals

Problem: Processes arrive at different times. Apply non-preemptive SJF.

ProcessATBT
P106
P218
P327
P433

Step-by-Step Timeline

📌 What Happens Each Second
t=0
Only P1 has arrived. Dispatch P1. Non-preemptive → runs to end.
t=0-6
P1 runs. P2 (t=1), P3 (t=2), P4 (t=3) arrive & wait. Queue at t=6: {P2:8, P3:7, P4:3}.
t=6
Pick shortest → P4 (burst 3). Runs 6→9.
t=9
Queue: {P2:8, P3:7}. Shortest = P3. Runs 9→16.
t=16
Only P2 remains. Runs 16→24.

Animated Gantt Chart

Non-Preemptive SJF — P1 runs first because it's alone
Order: P1(0-6) → P4(6-9) → P3(9-16) → P2(16-24) P1 P2 P3 P4 P1 P4 P3 P2 0 6 9 16 24 Step 1: t=0. Only P1 available. Dispatch P1. It runs 0→6 (non-preemptive). Step 2: t=6. Queue={P2:8,P3:7,P4:3}. Pick P4 (shortest). Runs 6→9. Step 3: t=9. Queue={P2:8,P3:7}. Pick P3. Runs 9→16. Step 4: Only P2 remains. Runs 16→24. Avg WT = (0 + 15 + 7 + 3)/4 = 6.25 ms Avg TAT = (6 + 23 + 14 + 6)/4 = 12.25 ms

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).

ProcessATBTStartCTTATWT
P1060660
P4336963
P327916147
P21816242315
Averages 12.25 6.25

Section 07

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.

ProcessATBT
P106
P218
P327
P433

Second-By-Second Reasoning

⏲ Preemption Decision Table (Remaining Times)
t=0
Only P1 (rem=6). Dispatch P1.
t=1
P2 arrives (rem=8). P1 rem = 5. P1 shorter, keep P1.
t=2
P3 arrives (rem=7). P1 rem = 4. P1 shorter, keep P1.
t=3
P4 arrives (rem=3). P1 rem = 3. Tie → keep P1 (was already running).
t=6
P1 completes. Queue rem = {P2:8, P3:7, P4:3}. Shortest = P4. Dispatch P4.
t=9
P4 done. Queue = {P2:8, P3:7}. Shortest = P3. Dispatch P3.
t=16
P3 done. Only P2 left. Runs 16→24.
💡
Why This Case Doesn't Show Preemption

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

ProcessATBT
P108
P214
P329
P435
✅ Preemption Trace
t=0
Only P1 (rem=8). Dispatch P1.
t=1
P2 arrives (rem=4). P1 rem = 7. PREEMPT! P2 shorter. Dispatch P2.
t=2
P3 arrives (rem=9). P2 rem = 3. P2 still shortest.
t=3
P4 arrives (rem=5). P2 rem = 2. P2 still shortest.
t=5
P2 completes. Rem: {P1:7, P3:9, P4:5}. Dispatch P4 (5).
t=10
P4 completes. Rem: {P1:7, P3:9}. Dispatch P1 (7).
t=17
P1 completes. Dispatch P3 (9). Ends at t=26.

Animated Preemptive SJF Gantt

SRTF — Preemption At t=1 Steals CPU From P1
SRTF Order: P1 → P2 (preempt!) → P4 → P1 (resume) → P3 P1 P2 P3 P4 P1 P2 PREEMPT! P4 P1 (resumes) P3 0 1 5 10 17 26 Preemption Events: t=1: P2 (rem=4) < P1 (rem=7) → PREEMPT P1, dispatch P2 t=2..3: newcomers longer than P2's remaining → no preemption Avg WT (SRTF) = (9 + 0 + 15 + 2)/4 = 6.5 ms   vs   Non-Preemptive SJF = 7.75 ms

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

ProcessATBTCTTATWT
P10817179
P214540
P329262415
P4351072
Averages 13.00 6.50

Section 08

Numerical 4 — Starvation Demonstration

SJF's dark side: if short jobs keep arriving, long jobs may never run. This is called starvation.

ProcessATBT
P1 (long)020
P212
P332
P452
P572
P692
P1 Keeps Getting Preempted — Watch It Starve
Under SRTF: P1 waits for every short newcomer P2 P3 P4 P5 P6 P1 (finally resumes for 19 more ms) 0 30 ms P1 arrived at t=0 but doesn't finish until t=30. TAT(P1) = 30 ms. If more short jobs arrived every 2 ms forever, P1 would starve — never complete.

Every short newcomer keeps preempting P1. In a real system with continuous short arrivals, P1 would starve indefinitely.

🔥
The Fix — Aging

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.


Section 09

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.

Exponential Average
τn+1 = α · tn + (1−α) · τn
tn = actual last burst, τn = previous prediction, α ∈ [0,1].
Common Choice
α = 0.5
Balances history and recent behaviour equally. Widely used in textbook examples.
# 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)
OUTPUT
predicted=10.00 actual=6 predicted= 8.00 actual=4 predicted= 6.00 actual=6 predicted= 6.00 actual=4 predicted= 5.00 actual=13 predicted= 9.00 actual=13 predicted=11.00 actual=13

Section 10

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))
OUTPUT
=== Non-Preemptive SJF === P1 start= 0 CT= 8 TAT= 8 WT= 0 P2 start= 8 CT=12 TAT=11 WT= 7 P4 start=12 CT=17 TAT=14 WT= 9 P3 start=17 CT=26 TAT=24 WT=15 Avg TAT=14.25 Avg WT=7.75 === Preemptive SJF (SRTF) === P1 start= 0 CT=17 TAT=17 WT= 9 P2 start= 1 CT= 5 TAT= 4 WT= 0 P3 start=17 CT=26 TAT=24 WT=15 P4 start= 5 CT=10 TAT= 7 WT= 2 Avg TAT=13.00 Avg WT=6.50

Section 11

SJF vs FCFS Side-By-Side

PropertyFCFSSJF (Non-Preemptive)SRTF (Preemptive)
Selection criterionArrival orderShortest burst among readyShortest remaining time
PreemptionNoNoYes
Avg waiting timeOften poorOptimal (given arrivals)Globally optimal
Starvation riskNoneYesYes
OverheadVery lowLow (one sort per dispatch)High (check on every arrival)
Requires burst predictionNoYesYes
Convoy effectSevereSolves itSolves it

Section 12

Advantages & Disadvantages

👍 Advantages
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
👎 Disadvantages
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

Section 13

Golden Rules

🏆 SJF Scheduling — Non-Negotiable Rules
1
Selection = smallest burst among ready processes. If a process hasn't arrived yet, it doesn't count — even if its burst is 1 ms.
2
Ties break by arrival order. If two processes have the same burst, run the earlier arrival first. This keeps the algorithm deterministic.
3
Non-preemptive SJF: selection happens only when CPU is free. Preemptive SJF (SRTF): selection re-evaluates on every arrival.
4
SJF minimises average waiting time — provably. No other algorithm can do better if you know burst lengths in advance.
5
Watch for starvation. If short jobs arrive continuously, long ones may never run. Combat with aging: bump priority of waiting processes over time.
6
In real systems, use exponential averagingn+1 = α · tn + (1−α) · τn) to predict the next burst. Common α = 0.5.
7
For a Gantt chart: always draw it before filling the metrics table. Start with arrivals as tick marks, then fill blocks in dispatch order.
8
SRTF is almost never used verbatim in modern OS kernels — it's too costly to re-decide on every arrival. But its spirit lives on in Multi-Level Feedback Queues (MLFQ) and the Linux CFS scheduler.