Operating Systems Lesson 2 of 6 ⏱️ 6 min read

CPU Scheduling Algorithms

FCFS, SJF, Round Robin and priority scheduling explained with worked examples of waiting and turnaround time.

Updated Oct 30, 2026

What is CPU scheduling?

There are usually far more ready threads than CPU cores. The scheduler decides which one runs next and for how long. Good scheduling keeps the CPU busy, finishes work quickly and keeps interactive apps responsive.

Key terms

Term Meaning
Arrival time When the process becomes ready
Burst time How much CPU time it needs
Completion time When it finishes
Turnaround time Completion − arrival (total time in the system)
Waiting time Turnaround − burst (time spent waiting in the ready queue)
Response time First time it runs − arrival (matters for interactive apps)
Throughput Processes completed per unit of time

Scheduling can be non-preemptive (a process runs until it finishes or blocks) or preemptive (the OS can interrupt it, e.g. when its time slice ends).

We'll compare algorithms on the same workload:

Process Arrival Burst
P1 0 8
P2 1 4
P3 2 9
P4 3 5

First-Come, First-Served (FCFS)

Run processes in arrival order, each to completion. Simple and fair in order — but short jobs stuck behind long ones wait a long time (the convoy effect).

Plaintext
Gantt: | P1 0–8 | P2 8–12 | P3 12–21 | P4 21–26 |
Process Completion Turnaround Waiting
P1 8 8 0
P2 12 11 7
P3 21 19 10
P4 26 23 18
Average 15.25 8.75

Shortest Job First (SJF)

When the CPU is free, pick the ready process with the smallest burst. SJF gives the minimum average waiting time of all non-preemptive algorithms — but it needs to know burst times in advance (real systems estimate them), and long jobs can starve.

Plaintext
Gantt: | P1 0–8 | P2 8–12 | P4 12–17 | P3 17–26 |

At time 8, P2 (4), P3 (9) and P4 (5) are ready, so P2 runs, then P4, then P3. The preemptive version, Shortest Remaining Time First (SRTF), switches whenever a shorter job arrives.

Round Robin (RR)

Each process gets a fixed time quantum (e.g. 3 units). If it hasn't finished, it goes to the back of the queue. Round Robin is preemptive and great for response time — every process gets the CPU regularly — which is why time-sharing systems are built on it.

  • Quantum too large → behaves like FCFS.
  • Quantum too small → too much time is wasted on context switches.

Priority scheduling

Each process has a priority; the highest runs first. Risk: low-priority processes starve. The fix is aging — gradually increasing the priority of processes that have waited a long time.

Simulating the algorithms

Python
from collections import deque

processes = [  # (name, arrival time, burst time)
    ("P1", 0, 8),
    ("P2", 1, 4),
    ("P3", 2, 9),
    ("P4", 3, 5),
]

def report(algorithm, finish):
    waits, turnarounds = [], []
    for name, arrival, burst in processes:
        turnaround = finish[name] - arrival
        turnarounds.append(turnaround)
        waits.append(turnaround - burst)
    n = len(processes)
    print(f"{algorithm:<16} avg waiting = {sum(waits) / n:5.2f}   avg turnaround = {sum(turnarounds) / n:5.2f}")

def fcfs():
    time, finish = 0, {}
    for name, arrival, burst in sorted(processes, key=lambda p: p[1]):
        time = max(time, arrival) + burst
        finish[name] = time
    return finish

def sjf():
    """Non-preemptive shortest job first."""
    time, finish, pending = 0, {}, list(processes)
    while pending:
        ready = [p for p in pending if p[1] <= time] or [min(pending, key=lambda p: p[1])]
        job = min(ready, key=lambda p: p[2])
        name, arrival, burst = job
        time = max(time, arrival) + burst
        finish[name] = time
        pending.remove(job)
    return finish

def round_robin(quantum):
    time, finish = 0, {}
    remaining = {name: burst for name, _, burst in processes}
    arrivals = deque(sorted(processes, key=lambda p: p[1]))
    queue = deque()
    while arrivals or queue:
        if not queue:
            time = max(time, arrivals[0][1])
        while arrivals and arrivals[0][1] <= time:
            queue.append(arrivals.popleft()[0])
        name = queue.popleft()
        run = min(quantum, remaining[name])
        time += run
        remaining[name] -= run
        while arrivals and arrivals[0][1] <= time:      # new arrivals join before the preempted process
            queue.append(arrivals.popleft()[0])
        if remaining[name]:
            queue.append(name)
        else:
            finish[name] = time
    return finish

report("FCFS", fcfs())
report("SJF", sjf())
report("Round Robin q=3", round_robin(3))

Output:

Plaintext
FCFS             avg waiting =  8.75   avg turnaround = 15.25
SJF              avg waiting =  7.75   avg turnaround = 14.25
Round Robin q=3  avg waiting = 13.50   avg turnaround = 20.00

Round Robin has the worst average waiting time here, yet it's what interactive systems use. Why? Its goal is response time: every process gets its first turn by time 9 (P4 starts after waiting 6 units), instead of P4 waiting 18 units before it runs at all under FCFS. Averages don't tell the whole story — choose the metric that matters for the workload.

Comparison

Algorithm Preemptive? Strength Weakness
FCFS No Simple, no starvation Convoy effect, poor response time
SJF / SRTF No / Yes Lowest average waiting time Needs burst estimates, starvation
Round Robin Yes Good response time, fair Higher turnaround, quantum tuning
Priority Either Important work first Starvation without aging
Multilevel feedback queue Yes Adapts: favours interactive jobs Complex to tune

What real operating systems use

  • Linux used the Completely Fair Scheduler (CFS), which tracks how much CPU time each task has had and runs the one that's had the least; since kernel 6.6 it uses EEVDF, a refinement that also considers latency.
  • Windows and macOS use priority-based, preemptive schedulers with multilevel queues that boost interactive threads.

Containers and Kubernetes add another layer: CPU limits throttle a container when it uses its quota, which can cause latency spikes in busy services.

Common mistakes

  • Mixing up turnaround and waiting time — waiting excludes the process's own burst.
  • Forgetting arrival times when drawing Gantt charts.
  • Assuming one algorithm is "best" — it depends on whether you optimise throughput, waiting or response time.

Key takeaways

  • Turnaround = completion − arrival; waiting = turnaround − burst.
  • FCFS is simple but suffers the convoy effect; SJF minimises average waiting time.
  • Round Robin trades turnaround for responsiveness.
  • Real schedulers combine priorities, fairness and preemption.
Finished this lesson?
Tick it off to track your Operating Systems progress.
Next lesson