GeeksforGeeks Easy

Shortest Job First

Shortest Job First: given the burst times of processes all available at time zero, schedule them to minimise the average waiting time and return that average.

Constraints
  • 1 <= n <= 10⁵
  • 1 <= burst[i] <= 10⁹
  • All processes are available at time zero
greedysortingscheduling
Open on GeeksforGeeks ↗
02

Intuition

Shortest job first scheduling selects the waiting process with the smallest burst time whenever the CPU becomes free. The goal is minimising average waiting time, and SJF is provably optimal for that metric — no other non-preemptive algorithm does better. The argument is an exchange: if a longer job runs before a shorter one, swapping them reduces the total waiting time, because the longer job's duration is then added to fewer processes' waits. Two variants exist, and confusing them is the usual source of wrong answers: - Non-preemptive SJF runs a chosen process to completion; preemptive SJF, also called Shortest Remaining Time First, interrupts the running process whenever a shorter job arrives. Both need arrival times respected. Only processes that have already arrived are eligible, so the selection is over the arrived-and-waiting set, not over all processes. When no process has arrived yet, the CPU idles until the next arrival. Skipping that idle period is a common bug — the clock must jump forward to the earliest arrival rather than scheduling something unavailable. A min-heap keyed by burst time makes selection O(log n), with processes pushed as their arrival time is reached. Sorting alone is not enough, since eligibility changes over time. Waiting time is completion − arrival − burst, and turnaround is completion − arrival. Getting these formulas backwards is easy and produces plausible-looking totals. The practical objection to SJF is starvation: long processes may never run if short ones keep arriving. That is why real schedulers use aging or multi-level queues rather than pure SJF.

How to spot this pattern

Running the shortest job first minimises average waiting time, because a job's duration delays every job queued behind it. Putting the cheapest delays first means the expensive one is paid by the fewest jobs — a classic exchange argument.

03

Approach

Try it first

Before reading on: price up what the direct approach costs here, then ask why the locally best choice is safe to commit to and never revisit. Aim for O(n log n) time and O(1) space.

1

Understand why it is optimal

Running a shorter job first reduces total waiting time, since a long job's duration is then added to fewer processes' waits. This exchange argument proves optimality.

2

Distinguish the two variants

Non-preemptive runs to completion; preemptive (Shortest Remaining Time First) interrupts when a shorter job arrives. Confusing them is the usual source of wrong answers.

3

Select only from arrived processes

Eligibility depends on arrival time, so the choice is over processes already waiting — not over all processes in the input.

4

Idle when nothing has arrived

Advance the clock to the next arrival when no process is available. Scheduling an unarrived process is a common and silent error.

5

Use a min-heap on burst time

Push processes as their arrival time passes and pop the shortest. Sorting alone is insufficient, since eligibility changes as the clock advances.

6

Compute the metrics carefully

Waiting is completion − arrival − burst; turnaround is completion − arrival. Reversing these produces plausible but wrong averages.

7

Note the starvation problem

Long processes may never run if short ones keep arriving. Real schedulers add aging or multi-level queues rather than using pure SJF.

8

Cost of the simulation

Each process is pushed and popped once, giving O(n log n) time and O(n) space for the heap.

04

Solution & live demo

▶1class Solution:
▶2 def solve(self, bt):
▶3 bt.sort()
▶4 clock = 0
▶5 total = 0
▶6 for t in bt:
▶7 total += clock
▶8 clock += t
▶9 return total // len(bt)
05

Common pitfalls

Adding the current job's own time to its wait

✗ Wrong
clock += t
total += clock
✓ Right
total += clock
clock += t

Waiting time is what elapses before a job starts, not including its own run. Advancing the clock first charges each job for its own duration and inflates every wait.

Sorting by arrival or leaving unsorted

✗ Wrong
# process in the given order
✓ Right
bt.sort()

The ordering is the entire algorithm — without it this is just first-come-first-served, which has a strictly worse average whenever a long job precedes a short one.

Returning the total instead of the average

✗ Wrong
return total
✓ Right
return total // len(bt)

The question asks for average waiting time. The unnormalised sum grows with the job count and isn't comparable across inputs.

06

Edge cases

Single job

It waits 0, so the average is 0.

All jobs the same length

Order does not matter; the average is the same for every permutation.

Integer division

Most judges expect the floor of the average, so use integer division rather than a float.

Jobs with different arrival times

Out of scope for this version — that variant needs a priority queue and preemption.

07

Complexity

Time
O(n log n)
Space
O(1)
Sorting dominates; the sweep uses two integers.