SJF Scheduling — New Short Jobs Starve Long Builds
- 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.
- 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
Quick Debug: Spotting Starvation in SJF Schedulers
A process has been waiting > 10 seconds after arrival
ps -eo pid,comm,etime,pcpu --sort=etime | head -20cat /proc/[pid]/sched | grep 'wait_sum'Scheduler log shows no progress for a high-burst job
sar -u 1 10 | grep averagegrep 'context_switch' /proc/statProduction Incident
Production Debug GuideSymptoms and actions for production schedulers using SJF-like logic
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).
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}")
P4: WT=7, TAT=8
P3: WT=8, TAT=10
P2: WT=11, TAT=15
Avg WT: 6.50
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.
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}")
P2: WT=1, TAT=5
P3: WT=0, TAT=2
P4: WT=0, TAT=1
Avg WT: 2.50
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 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.
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.
| Property | SJF Non-Preemptive | SRTF (Preemptive SJF) | SJF + Aging |
|---|---|---|---|
| Average Waiting Time | Optimal (non-preemptive) | Optimal (all) | Near-optimal (slightly worse for short jobs) |
| Fairness | Poor (starvation risk) | Moderate (starvation still possible) | Good (every process makes progress) |
| Context Switches | Minimal (1 per job) | High (at every arrival) | Moderate (depends on aging preemption) |
| Implementation Complexity | Low | Medium (track remaining times) | Medium (track waiting times) |
| Production Feasibility | Low (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
Interview Questions on This Topic
- QProve that SJF minimises average waiting time among non-preemptive algorithms.SeniorReveal
- QWhat is the difference between SJF and SRTF?Mid-levelReveal
- QHow does aging solve the starvation problem in SJF?Mid-levelReveal
- QCalculate average waiting time for SRTF: P1(AT=0,BT=8), P2(AT=1,BT=4), P3(AT=2,BT=2).JuniorReveal
- QWhy is SJF not used directly in modern operating systems?SeniorReveal
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.
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.