Skip to main content

CPU Scheduling Problem: FCFS, SJF, SRTF, and Round Robin Walkthrough

Medium

Interview Question: "Given four processes with varying arrival times and burst times, construct Gantt charts and compute completion time (CT), turnaround time (TAT), and waiting time (WT) for FCFS, Non-Preemptive SJF, Preemptive SRTF, and Round Robin (q = 3 ms). Why does SRTF achieve minimal average waiting time, and why is it rarely used in production kernels?"


Problem Specification & Dataset​

Consider four processes arriving in the ready queue with the following characteristics:

ProcessArrival Time (AT)Burst Time (BT)
P10 ms8 ms
P21 ms4 ms
P32 ms9 ms
P43 ms5 ms

Standard Scheduling Formulas:​

  • Turnaround Time (TAT): Total elapsed time from arrival to completion. TAT=CT−ATTAT = CT - AT
  • Waiting Time (WT): Total time spent waiting in the ready queue without CPU allocation. WT=TAT−BTWT = TAT - BT

Part 1: First-Come, First-Served (FCFS)​

FCFS executes processes strictly in order of their arrival without preemption.

Gantt Chart​

[ P1 (8ms) ][ P2 (4ms) ][ P3 (9ms) ][ P4 (5ms) ]
0 8 12 21 26

Step-by-Step Calculation Table​

ProcessATBTCompletion (CT)Turnaround Time (TAT = CT - AT)Waiting Time (WT = TAT - BT)
P10 ms8 ms8 ms8 - 0 = 8 ms8 - 8 = 0 ms
P21 ms4 ms12 ms12 - 1 = 11 ms11 - 4 = 7 ms
P32 ms9 ms21 ms21 - 2 = 19 ms19 - 9 = 10 ms
P43 ms5 ms26 ms26 - 3 = 23 ms23 - 5 = 18 ms
  • Average Turnaround Time: (8 + 11 + 19 + 23) / 4 = 61 / 4 = 15.25 ms
  • Average Waiting Time: (0 + 7 + 10 + 18) / 4 = 35 / 4 = 8.75 ms

The Convoy Effect: P4 only requires 5 ms of computation, but sat waiting for 18 ms behind the long burst of P3. FCFS heavily penalizes short jobs when scheduled behind long CPU-bound tasks.


Part 2: Shortest Job First (SJF) — Non-Preemptive​

In Non-Preemptive SJF, once a process begins execution, it runs to completion. Whenever the CPU becomes idle, the scheduler dispatches the available arrived process with the shortest burst time.

Execution Timeline:​

  • t = 0 ms: Only P1 has arrived. P1 starts and runs uninterrupted until t = 8 ms.
  • t = 8 ms: P1 finishes. Arrived processes in ready queue: P2 (BT = 4 ms), P3 (BT = 9 ms), P4 (BT = 5 ms). Shortest job is P2. Runs t = 8 -> 12 ms.
  • t = 12 ms: P2 finishes. Remaining in queue: P4 (BT = 5 ms), P3 (BT = 9 ms). Shortest job is P4. Runs t = 12 -> 17 ms.
  • t = 17 ms: P4 finishes. Remaining in queue: P3 (BT = 9 ms). Runs t = 17 -> 26 ms.

Gantt Chart​

[ P1 (8ms) ][ P2 (4ms) ][ P4 (5ms) ][ P3 (9ms) ]
0 8 12 17 26

Step-by-Step Calculation Table​

ProcessATBTCompletion (CT)Turnaround Time (TAT = CT - AT)Waiting Time (WT = TAT - BT)
P10 ms8 ms8 ms8 - 0 = 8 ms8 - 8 = 0 ms
P21 ms4 ms12 ms12 - 1 = 11 ms11 - 4 = 7 ms
P43 ms5 ms17 ms17 - 3 = 14 ms14 - 5 = 9 ms
P32 ms9 ms26 ms26 - 2 = 24 ms24 - 9 = 15 ms
  • Average Turnaround Time: (8 + 11 + 14 + 24) / 4 = 57 / 4 = 14.25 ms
  • Average Waiting Time: (0 + 7 + 9 + 15) / 4 = 31 / 4 = 7.75 ms

Part 3: Shortest Remaining Time First (SRTF) — Preemptive SJF​

In SRTF, if a newly arrived process has a remaining burst time strictly less than the currently running process, the current process is preempted immediately.

Execution Timeline:​

  • t = 0 ms: P1 starts running (BT = 8 ms).
  • t = 1 ms: P2 arrives with BT = 4 ms. P1 has remaining burst 7 ms. Because 4 < 7, P2 preempts P1!
  • t = 1 -> 5 ms: P2 runs to completion at t = 5 ms. During this interval, P3 arrives at t = 2 ms (BT = 9 ms) and P4 arrives at t = 3 ms (BT = 5 ms).
  • t = 5 ms: Ready queue: P4 (BT = 5 ms), P1 (rem = 7 ms), P3 (BT = 9 ms). Shortest remaining job is P4. Runs t = 5 -> 10 ms.
  • t = 10 ms: P4 completes. Ready queue: P1 (rem = 7 ms), P3 (BT = 9 ms). Shortest remaining job is P1. Runs t = 10 -> 17 ms.
  • t = 17 ms: P1 completes. Only P3 remains (rem = 9 ms). Runs t = 17 -> 26 ms.

Gantt Chart​

[P1][ P2 (4ms) ][ P4 (5ms) ][ P1 (7ms) ][ P3 (9ms) ]
0 1 5 10 17 26

Step-by-Step Calculation Table​

ProcessATBTCompletion (CT)Turnaround Time (TAT = CT - AT)Waiting Time (WT = TAT - BT)
P10 ms8 ms17 ms17 - 0 = 17 ms17 - 8 = 9 ms
P21 ms4 ms5 ms5 - 1 = 4 ms4 - 4 = 0 ms
P43 ms5 ms10 ms10 - 3 = 7 ms7 - 5 = 2 ms
P32 ms9 ms26 ms26 - 2 = 24 ms24 - 9 = 15 ms
  • Average Turnaround Time: (17 + 4 + 7 + 24) / 4 = 52 / 4 = 13.00 ms
  • Average Waiting Time: (9 + 0 + 2 + 15) / 4 = 26 / 4 = 6.50 ms

Part 4: Round Robin (RR, Quantum = 3 ms)​

Processes are dispatched in a circular FIFO queue for a maximum time quantum of q = 3 ms. If a process does not finish within 3 ms, it is preempted and returned to the tail of the ready queue.

Ready Queue Trace (q = 3 ms):​

  • t = 0 ms: P1 arrives. Queue: [P1]. P1 runs t = 0 -> 3 ms (remaining burst = 5 ms).
  • During this window: P2 arrives at t = 1 ms, P3 arrives at t = 2 ms, and P4 arrives at t = 3 ms.
  • Newly arrived P4 is enqueued before preempted P1. Queue becomes: [P2, P3, P4, P1].
  • t = 3 -> 6 ms: P2 runs 3 ms (remaining burst = 1 ms). Queue: [P3, P4, P1, P2].
  • t = 6 -> 9 ms: P3 runs 3 ms (remaining burst = 6 ms). Queue: [P4, P1, P2, P3].
  • t = 9 -> 12 ms: P4 runs 3 ms (remaining burst = 2 ms). Queue: [P1, P2, P3, P4].
  • t = 12 -> 15 ms: P1 runs 3 ms (remaining burst = 2 ms). Queue: [P2, P3, P4, P1].
  • t = 15 -> 16 ms: P2 runs its remaining 1 ms and completes at t = 16 ms! Queue: [P3, P4, P1].
  • t = 16 -> 19 ms: P3 runs 3 ms (remaining burst = 3 ms). Queue: [P4, P1, P3].
  • t = 19 -> 21 ms: P4 runs its remaining 2 ms and completes at t = 21 ms! Queue: [P1, P3].
  • t = 21 -> 23 ms: P1 runs its remaining 2 ms and completes at t = 23 ms! Queue: [P3].
  • t = 23 -> 26 ms: P3 runs its remaining 3 ms and completes at t = 26 ms! Queue empty.

Gantt Chart​

[ P1 ][ P2 ][ P3 ][ P4 ][ P1 ][P2][ P3 ][P4][P1][ P3 ]
0 3 6 9 12 15 16 19 21 23 26

Step-by-Step Calculation Table​

ProcessATBTCompletion (CT)Turnaround Time (TAT = CT - AT)Waiting Time (WT = TAT - BT)
P10 ms8 ms23 ms23 - 0 = 23 ms23 - 8 = 15 ms
P21 ms4 ms16 ms16 - 1 = 15 ms15 - 4 = 11 ms
P32 ms9 ms26 ms26 - 2 = 24 ms24 - 9 = 15 ms
P43 ms5 ms21 ms21 - 3 = 18 ms18 - 5 = 13 ms
  • Average Turnaround Time: (23 + 15 + 24 + 18) / 4 = 80 / 4 = 20.00 ms
  • Average Waiting Time: (15 + 11 + 15 + 13) / 4 = 54 / 4 = 13.50 ms

Comparative Scorecard & Engineering Analysis​

AlgorithmPreemptive?Avg Turnaround Time (TAT)Avg Waiting Time (WT)Best / Worst Property
SRTFYes13.00 ms (Optimal)6.50 ms (Lowest)Mathematically optimal average wait time, but causes severe starvation.
SJFNo14.25 ms7.75 msExcellent throughput, but non-preemptive convoy delays possible.
FCFSNo15.25 ms8.75 msMinimal scheduling overhead, but suffers from convoy effect.
Round Robin (q = 3 ms)Yes20.00 ms13.50 msHighest average wait time, but provides excellent interactive response time and zero starvation.

Why SRTF is Rejected in Production Operating Systems​

Although SRTF is mathematically proven to minimize average waiting time, modern kernels (Linux CFS, Windows NT) reject it for three critical reasons:

  1. The Clairvoyance Problem: SRTF requires knowing the future burst time of every thread before it executes. General-purpose kernels cannot predict how long a process will run before blocking on user input or disk I/O.
  2. Pathological Starvation: If a continuous stream of short 1 ms tasks arrives, large tasks (like video encoding or scientific simulations) will never receive CPU time.
  3. Context Switch Thrashing: In textbook models, context switches cost 0 ms. In physical silicon, saving registers, switching page tables (CR3), and cold-cache misses cost 2 to 5 microseconds. Aggressive preemption under SRTF degrades aggregate throughput.

Production Solution: General-purpose kernels use Multilevel Feedback Queues (MLFQ) or Completely Fair Scheduling (CFS) with virtual runtime (vruntime) to approximate the benefits of SRTF dynamically without requiring future knowledge or causing starvation.


Python Verification: Multi-Algorithm CPU Scheduling Simulator​

The following executable Python script implements FCFS, SJF, SRTF, and Round Robin, calculating exact completion times, Gantt traces, and waiting time averages:

"""
CPU Scheduling Algorithms Simulator
Demonstrates:
1. FCFS (First-Come, First-Served)
2. SJF (Shortest Job First - Non-Preemptive)
3. SRTF (Shortest Remaining Time First - Preemptive)
4. Round Robin (Time Quantum = 3)
"""

from typing import List, Dict, Tuple

class Process:
def __init__(self, pid: str, arrival: int, burst: int):
self.pid = pid
self.arrival = arrival
self.burst = burst
self.remaining = burst
self.completion = 0

def tat(self) -> int:
return self.completion - self.arrival

def wt(self) -> int:
return self.tat() - self.burst


def run_fcfs(procs: List[Process]) -> Tuple[float, float]:
p_sorted = sorted(procs, key=lambda p: p.arrival)
current_time = 0
for p in p_sorted:
if current_time < p.arrival:
current_time = p.arrival
current_time += p.burst
p.completion = current_time

avg_tat = sum(p.tat() for p in p_sorted) / len(p_sorted)
avg_wt = sum(p.wt() for p in p_sorted) / len(p_sorted)
return avg_tat, avg_wt


def run_sjf_non_preemptive(procs: List[Process]) -> Tuple[float, float]:
current_time = 0
completed = []
ready = []
unstarted = sorted(procs, key=lambda p: p.arrival)

while len(completed) < len(procs):
while unstarted and unstarted[0].arrival <= current_time:
ready.append(unstarted.pop(0))

if not ready:
current_time = unstarted[0].arrival
continue

ready.sort(key=lambda p: (p.burst, p.arrival))
active = ready.pop(0)
current_time += active.burst
active.completion = current_time
completed.append(active)

avg_tat = sum(p.tat() for p in completed) / len(completed)
avg_wt = sum(p.wt() for p in completed) / len(completed)
return avg_tat, avg_wt


def run_srtf(procs: List[Process]) -> Tuple[float, float]:
current_time = 0
completed = []
total = len(procs)

while len(completed) < total:
available = [p for p in procs if p.arrival <= current_time and p.remaining > 0]
if not available:
current_time += 1
continue

available.sort(key=lambda p: (p.remaining, p.arrival))
active = available[0]
active.remaining -= 1
current_time += 1

if active.remaining == 0:
active.completion = current_time
completed.append(active)

avg_tat = sum(p.tat() for p in completed) / len(completed)
avg_wt = sum(p.wt() for p in completed) / len(completed)
return avg_tat, avg_wt


def run_round_robin(procs: List[Process], quantum: int = 3) -> Tuple[float, float]:
current_time = 0
queue: List[Process] = []
unstarted = sorted(procs, key=lambda p: p.arrival)
completed = []

while len(completed) < len(procs):
while unstarted and unstarted[0].arrival <= current_time:
queue.append(unstarted.pop(0))

if not queue:
current_time = unstarted[0].arrival
continue

active = queue.pop(0)
run_slice = min(active.remaining, quantum)
active.remaining -= run_slice
current_time += run_slice

# Enqueue new arrivals that landed during this time slice
while unstarted and unstarted[0].arrival <= current_time:
queue.append(unstarted.pop(0))

if active.remaining == 0:
active.completion = current_time
completed.append(active)
else:
queue.append(active)

avg_tat = sum(p.tat() for p in completed) / len(completed)
avg_wt = sum(p.wt() for p in completed) / len(completed)
return avg_tat, avg_wt


def main():
print("=== CPU Scheduling Problem Verification ===\n")
dataset = [("P1", 0, 8), ("P2", 1, 4), ("P3", 2, 9), ("P4", 3, 5)]

# 1. FCFS
fcfs_procs = [Process(pid, arr, bst) for pid, arr, bst in dataset]
tat_fcfs, wt_fcfs = run_fcfs(fcfs_procs)
print(f"FCFS: Avg TAT = {tat_fcfs:5.2f} ms | Avg WT = {wt_fcfs:5.2f} ms")

# 2. SJF
sjf_procs = [Process(pid, arr, bst) for pid, arr, bst in dataset]
tat_sjf, wt_sjf = run_sjf_non_preemptive(sjf_procs)
print(f"SJF (NP): Avg TAT = {tat_sjf:5.2f} ms | Avg WT = {wt_sjf:5.2f} ms")

# 3. SRTF
srtf_procs = [Process(pid, arr, bst) for pid, arr, bst in dataset]
tat_srtf, wt_srtf = run_srtf(srtf_procs)
print(f"SRTF: Avg TAT = {tat_srtf:5.2f} ms | Avg WT = {wt_srtf:5.2f} ms [Optimal]")

# 4. Round Robin (q=3)
rr_procs = [Process(pid, arr, bst) for pid, arr, bst in dataset]
tat_rr, wt_rr = run_round_robin(rr_procs, quantum=3)
print(f"Round Robin: Avg TAT = {tat_rr:5.2f} ms | Avg WT = {wt_rr:5.2f} ms")

if __name__ == "__main__":
main()