Implementing SJF (Shortest Job First) Scheduling Algorithm in C++

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

  1. Process struct β€” Groups all attributes (processNo, arrivalTime, burstTime, waitingTime, turnaroundTime) into a single record. Storing processNo separately ensures we can display the original process identity after sorting changes the array order.
  2. Sorting by burstTime (ascending) β€” A nested selection sort compares processes[outer].burstTime against processes[inner].burstTime. When the outer is larger, the entire Process struct is swapped β€” preserving all fields together. After sorting, index 0 holds the shortest job.
  3. 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. The if (wt < 0) guard handles cases where a process arrived long after the previous one finished.
  4. 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

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.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.