CPU scheduling
In one line
The CPU scheduler decides which ready thread runs next and for how long, trading off throughput, response time and fairness.
What it is
A machine usually has far more runnable threads than CPU cores. The scheduler is the part of the OS that picks which ready thread gets a core next. It runs whenever a thread blocks, finishes, or (in preemptive scheduling) uses up its time slice or is overtaken by a higher-priority thread.
Classic algorithms are First-Come First-Served (FCFS), Shortest Job First (SJF) and its preemptive form Shortest Remaining Time First (SRTF), Priority scheduling, Round Robin (RR) with a fixed time slice called a quantum, and the Multilevel Feedback Queue (MLFQ). Real operating systems combine these ideas, for example Linux's Completely Fair Scheduler and its successor EEVDF.
Why interviewers ask
Scheduling questions test whether you can reason about trade-offs with numbers: average waiting time, response time, throughput and starvation. They also connect to real system behaviour, such as why a busy server feels slow, why priorities can starve tasks, and how job queues and thread pools decide what runs next.
How it works
- 1Key metrics: waiting time (time spent in the ready queue), turnaround time (finish time minus arrival time), response time (first time it runs minus arrival), throughput (jobs finished per unit of time) and CPU utilisation.
- 2FCFS runs jobs in arrival order without preemption. Simple, but one long job makes every short job behind it wait (the convoy effect).
- 3SJF runs the shortest job first and gives the minimum average waiting time when all jobs are known in advance; SRTF is its preemptive version. The catch: real burst lengths are unknown, so they are estimated (for example from past bursts), and long jobs can starve.
- 4Priority scheduling runs the highest-priority job first. Low-priority jobs can starve; aging (slowly raising the priority of jobs that have waited long) fixes this.
- 5Round Robin gives each job a quantum (for example 10-100 ms) in turn, then moves it to the back of the queue. It gives good response time; too small a quantum wastes time on context switches, too large turns it into FCFS.
- 6MLFQ uses several queues: new jobs start at high priority with short quanta; jobs that use their full quantum drop to lower queues. Interactive jobs stay responsive while long CPU-bound jobs still make progress. Periodic priority boosts prevent starvation.
Worked example · Three jobs, three schedulers (all arrive at time 0)
- Jobs: P1 needs 24 ms of CPU, P2 needs 3 ms, P3 needs 3 ms, arriving in that order.
- FCFS: P1 runs 0-24, P2 runs 24-27, P3 runs 27-30. Waiting times are 0, 24 and 27, average 17 ms. The two short jobs are stuck behind the long one.
- SJF: run P2 0-3, P3 3-6, then P1 6-30. Waiting times are P2 = 0, P3 = 3, P1 = 6, average 3 ms, the lowest possible here.
- Round Robin with a 4 ms quantum: P1 runs 0-4 (20 ms left, back of queue), P2 runs 4-7 and finishes, P3 runs 7-10 and finishes, then P1 runs alone 10-30.
- RR waiting times: P1 = 30 - 24 = 6, P2 = 7 - 3 = 4, P3 = 10 - 3 = 7, average about 5.67 ms. Worse than SJF, much better than FCFS, and it does not need to know burst lengths in advance.
Picture it
A single checkout counter: FCFS serves whoever came first, even if they have a full trolley. SJF lets the person with one item go first. Round Robin scans 4 items per customer, then sends them to the back of the line, so nobody waits too long to get started.
Reference code
from collections import deque
def round_robin(bursts, quantum):
"""bursts: {name: burst_time}, all arriving at time 0.
Returns each process's waiting time."""
remaining = dict(bursts)
ready = deque(bursts) # names in arrival order
time, finish = 0, {}
while ready:
p = ready.popleft()
run = min(quantum, remaining[p])
time += run
remaining[p] -= run
if remaining[p] > 0:
ready.append(p) # preempted: back of the queue
else:
finish[p] = time
# waiting time = finish time - burst time (arrival is 0)
return {p: finish[p] - bursts[p] for p in bursts}
print(round_robin({"P1": 24, "P2": 3, "P3": 3}, 4))
# {'P1': 6, 'P2': 4, 'P3': 7} -> average 5.67
Simulates Round Robin for jobs that all arrive at time 0 and returns each waiting time. With the textbook input the result matches the hand trace: 6, 4 and 7 ms. Each job is queued at most once at a time, so the loop runs once per time slice.
Key patterns
Compute waiting time by hand
When: Classic exam or interview scheduling question
How: Draw a timeline (Gantt chart), write each job's finish time, then waiting = turnaround - burst, and turnaround = finish - arrival.
Short jobs first
When: Minimising average waiting time
How: SJF or SRTF are optimal for average waiting time if lengths are known; estimate lengths from history when they are not.
Time slicing for responsiveness
When: Interactive systems with many users or tasks
How: Round Robin with a quantum well above the context-switch cost; MLFQ to favour interactive jobs.
Aging against starvation
When: Any priority-based scheme
How: Increase a job's priority the longer it waits so it eventually runs.
Real-world use
Linux CFS and EEVDF
Linux's Completely Fair Scheduler (default from kernel 2.6.23) tracked each task's virtual runtime in a red-black tree and always ran the task with the least, weighted by nice value. Kernel 6.6 replaced it with EEVDF, which also accounts for latency needs.
Windows
Windows uses preemptive priority scheduling with 32 priority levels and gives temporary priority boosts to threads that wake up from I/O or belong to the foreground window, keeping the UI responsive.
Kubernetes and containers
CPU requests and limits on pods are enforced by Linux cgroups through the kernel scheduler: a container over its CPU limit is throttled for the rest of the scheduling period, which can show up as latency spikes.
Job queues and thread pools
Systems like Celery or Sidekiq use separate queues with different priorities and worker counts, an application-level version of priority and multilevel queue scheduling.
In a project
Your background job system runs both quick jobs (sending emails, under 1 second) and long reports (several minutes) on the same 8 workers, and users complain that emails arrive minutes late.
- Measure queue wait time per job type; you find emails waiting behind reports, the convoy effect of FCFS.
- Split into two queues: a high-priority 'fast' queue and a 'slow' queue, with dedicated workers for each (for example 3 fast and 5 slow).
- Move any job that runs longer than a threshold into the slow queue next time, similar to MLFQ demotion.
- Add aging or a minimum worker share so slow jobs are never starved.
- Track p95 wait time per queue on a dashboard.
Impact: Email p95 wait time falls from several minutes to a few seconds, reports still finish within their usual window, and the team can tune worker counts per queue instead of guessing.
Trade-offs
Use it when
- FCFS: batch jobs where fairness by arrival and simplicity matter more than response time
- SJF or SRTF: minimising average waiting time when job lengths are known or predictable
- Round Robin or MLFQ: interactive and multi-user systems that need good response time
Avoid it when
- FCFS with a mix of very long and very short jobs (convoy effect)
- Strict priority without aging: low-priority work can starve forever
- A very small Round Robin quantum: context-switch overhead eats CPU time
Cost: Picking the next job is O(1) for FCFS and Round Robin queues and O(log n) with a heap or balanced tree for SJF, priority or CFS-style schedulers.
Common mistakes
- Mixing up waiting time and turnaround time (turnaround includes the run time).
- Saying SJF is always best: it needs burst lengths in advance and can starve long jobs.
- Forgetting that preemptive and non-preemptive versions of the same idea give different answers.
- Choosing a Round Robin quantum without considering context-switch cost.
- Ignoring starvation when proposing priority scheduling.
Interview questions
Quick revision
- Waiting = turnaround - burst; turnaround = finish - arrival.
- FCFS: simple, convoy effect.
- SJF: minimum average wait if lengths known; can starve long jobs. SRTF is the preemptive version.
- Priority: starvation, fixed by aging.
- Round Robin: quantum, good response; tiny quantum = overhead, huge = FCFS.
- MLFQ: demote CPU-heavy jobs, boost periodically.
- Linux: CFS (vruntime, red-black tree) then EEVDF from 6.6.
- Example 24/3/3: FCFS 17, SJF 3, RR(q=4) 5.67 ms average wait.
Check yourself
1. Jobs of 24, 3 and 3 ms arrive at time 0 in that order. What is the FCFS average waiting time?
2. Which algorithm gives the minimum average waiting time when all burst lengths are known in advance?
3. What happens if the Round Robin quantum is made very large?
4. What is aging in CPU scheduling?
Saved on this device. Create a free account to keep it on every device.
Practise this with Sam
A real mock interview, out loud, with an honest scorecard. Free to start.