In this post, we implement the Shortest Job First (SJF) CPU scheduling algorithm in C++. SJF always selects the process with the smallest burst time from the ready queue and executes it next. This greedy approach provably minimizes the average waiting time, making SJF theoretically optimal among all non-preemptive algorithms when all jobs are available simultaneously.
What is SJF Scheduling?
In SJF, the scheduler sorts all ready processes by their burst (execution) time in ascending order and then runs them in that sequence. The shortest job finishes earliest, releasing the CPU quickly for others and keeping the average wait low.
It is non-preemptive β once started, a process runs to completion. The preemptive variant is called Shortest Remaining Time First (SRTF).
- Waiting Time (WT):
WT[i] = WT[i-1] + BurstTime[i-1] - (ArrivalTime[i] - ArrivalTime[i-1]) - Turnaround Time (TT):
TT[i] = WT[i] + BurstTime[i]
The key risk with SJF is starvation: if short jobs keep arriving, long jobs may never execute.
C++ Code Implementation
// ============================================================
// Shortest Job First (SJF) CPU Scheduling Algorithm in C++
// Non-preemptive: selects process with smallest burst time first
// ============================================================
#include <iostream>
using namespace std;
// Structure to group all attributes of a single process together
struct Process {
int processNo; // Original process number (preserved after sorting)
int arrivalTime; // Time at which process arrives in the ready queue
int burstTime; // CPU burst time required by the process
int waitingTime; // Time spent waiting before CPU is allocated
int turnaroundTime; // Total time from arrival to completion
};
int main() {
const int PROCESS_COUNT = 4;
Process processes[PROCESS_COUNT];
// -------------------------------------------------------
// Step 1: Read arrival and burst times from the user
// -------------------------------------------------------
for (int i = 0; i < PROCESS_COUNT; i++) {
cout << "\n Enter arrival time of P" << i + 1 << ": ";
cin >> processes[i].arrivalTime;
cout << " Enter burst time of P" << i + 1 << ": ";
cin >> processes[i].burstTime;
processes[i].processNo = i + 1; // Remember original process number
}
// Display the entered data before sorting
cout << "\n\n Entered Data (before sorting)";
cout << "\n PNO\tAT\tBT";
for (int i = 0; i < PROCESS_COUNT; i++) {
cout << "\n P" << processes[i].processNo
<< "\t" << processes[i].arrivalTime
<< "\t" << processes[i].burstTime;
}
// -------------------------------------------------------
// Step 2: Sort processes by burst time (ascending = SJF order)
// Uses selection sort; swaps entire Process struct to keep data intact
// -------------------------------------------------------
for (int outer = 0; outer < PROCESS_COUNT - 1; outer++) {
for (int inner = outer + 1; inner < PROCESS_COUNT; inner++) {
if (processes[outer].burstTime > processes[inner].burstTime) {
// Swap the entire process record
Process temp = processes[outer];
processes[outer] = processes[inner];
processes[inner] = temp;
}
}
}
// After sorting: processes[0] has the shortest burst time
// -------------------------------------------------------
// Step 3: Compute Waiting Time and Turnaround Time
// -------------------------------------------------------
processes[0].waitingTime = 0; // First job waits 0
processes[0].turnaroundTime = processes[0].burstTime; // TT = 0 + BT
int totalBurstTime = processes[0].burstTime;
for (int i = 1; i < PROCESS_COUNT; i++) {
// WT[i] = WT[i-1] + BT[i-1] - gap in arrival times
processes[i].waitingTime =
processes[i-1].waitingTime + processes[i-1].burstTime
- (processes[i].arrivalTime - processes[i-1].arrivalTime);
// Guard: waiting time cannot go negative
if (processes[i].waitingTime < 0) processes[i].waitingTime = 0;
processes[i].turnaroundTime =
processes[i].waitingTime + processes[i].burstTime;
totalBurstTime += processes[i].burstTime;
}
// -------------------------------------------------------
// Step 4: Print results
// -------------------------------------------------------
cout << "\n\n Result (sorted by burst time β shortest first)";
cout << "\n PNO\tAT\tWT\tBT\tTT";
for (int i = 0; i < PROCESS_COUNT; i++) {
cout << "\n P" << processes[i].processNo
<< "\t" << processes[i].arrivalTime
<< "\t" << processes[i].waitingTime
<< "\t" << processes[i].burstTime
<< "\t" << processes[i].turnaroundTime;
}
cout << "\n Total Burst Time: " << totalBurstTime;
// -------------------------------------------------------
// Step 5: Compute averages
// -------------------------------------------------------
int totalWaiting = 0, totalTurnaround = 0;
for (int i = 0; i < PROCESS_COUNT; i++) {
totalWaiting += processes[i].waitingTime;
totalTurnaround += processes[i].turnaroundTime;
}
cout << "\n Average Waiting Time : " << (float)totalWaiting / PROCESS_COUNT;
cout << "\n Average Turnaround Time : " << (float)totalTurnaround / PROCESS_COUNT;
cout << endl;
return 0;
}
Explanation of the Code
- Process struct β Groups all attributes (processNo, arrivalTime, burstTime, waitingTime, turnaroundTime) into a single record. Storing
processNoseparately ensures we can display the original process identity after sorting changes the array order. - Sorting by burstTime (ascending) β A nested selection sort compares
processes[outer].burstTimeagainstprocesses[inner].burstTime. When the outer is larger, the entireProcessstruct is swapped β preserving all fields together. After sorting, index 0 holds the shortest job. - Waiting Time formula β
WT[i] = WT[i-1] + BT[i-1] - (AT[i] - AT[i-1]). The subtracted term accounts for the gap between consecutive arrival times, preventing double-counting idle CPU time. Theif (wt < 0)guard handles cases where a process arrived long after the previous one finished. - Turnaround Time β
TT = WT + BurstTime. Averages are computed by summing all values and dividing by the process count.
Sample Output
Enter arrival time of P1: 0
Enter burst time of P1: 5
Enter arrival time of P2: 0
Enter burst time of P2: 3
Enter arrival time of P3: 0
Enter burst time of P3: 8
Enter arrival time of P4: 0
Enter burst time of P4: 4
Entered Data (before sorting)
PNO AT BT
P1 0 5
P2 0 3
P3 0 8
P4 0 4
Result (sorted by burst time β shortest first)
PNO AT WT BT TT
P2 0 0 3 3
P4 0 3 4 7
P1 0 7 5 12
P3 0 12 8 20
Total Burst Time: 20
Average Waiting Time : 5.5
Average Turnaround Time : 10.5
Step-by-Step Explanation of Input/Output
Input: All 4 processes arrive at time 0. After sorting by burst time: P2(BT=3) β P4(BT=4) β P1(BT=5) β P3(BT=8).
Gantt Chart:
| P2 (0β3) | P4 (3β7) | P1 (7β12) | P3 (12β20) |
- P2 (BT=3): Runs first (shortest). WT = 0. TT = 0 + 3 = 3.
- P4 (BT=4): Starts at time 3. WT = 3. TT = 3 + 4 = 7.
- P1 (BT=5): Starts at time 7. WT = 7. TT = 7 + 5 = 12.
- P3 (BT=8): Runs last (longest). WT = 12. TT = 12 + 8 = 20.
- Average WT = (0 + 3 + 7 + 12) / 4 = 22 / 4 = 5.5
- Average TT = (3 + 7 + 12 + 20) / 4 = 42 / 4 = 10.5
Compare with FCFS on the same input (arrival order P1βP2βP3βP4): FCFS average WT = (0+5+8+16)/4 = 7.25. SJF reduces average waiting time from 7.25 to 5.5 β a clear improvement.
See Also
- Implementing FCFS Scheduling Algorithm in C++
- Implementing Round Robin Scheduling Algorithm in C++
- Implementation of Priority Scheduling Algorithm in C++
Bottom line isβ¦
SJF is the optimal non-preemptive scheduling algorithm for minimizing average waiting time. Its main limitations are that burst times must be known in advance (often unrealistic) and that long processes risk starvation if short ones keep arriving. In practice, SJF is approximated by predicting burst times based on historical execution patterns. Its preemptive variant, SRTF, offers even better responsiveness but requires more overhead.