operating systems20 min

Context Switching

The cost of switching between processes or threads

0/9Not Started

Why This Matters

Every time the OS switches from one process to another, it must save the entire state of the running process and load the state of the next one. This is a context switch, and it is not free. Each switch costs precious microseconds of CPU time where no useful work gets done.

Understanding context switching explains why spawning thousands of threads can actually slow your program down, why Go uses lightweight goroutines instead of OS threads, and why the event loop model in Node.js avoids the problem entirely. It is the hidden tax on multitasking.

Define Terms

Visual Model

Process Arunning
CPU
Process Bwaiting
Save A Stateto PCB
Load B Statefrom PCB
Overhead1-10 us
interrupt
switch
resume

The full process at a glance. Click Start tour to walk through each step.

A context switch saves one process state and loads another, with overhead during the transition.

Code Example

Code
// Demonstrating context switch impact:
// Thread pool vs Event loop approach

// --- Simulating thread-per-task overhead ---
function simulateThreadPool(tasks, switchCostMs) {
  let totalTime = 0;
  let switches = 0;

  for (let i = 0; i < tasks.length; i++) {
    totalTime += tasks[i].duration; // actual work
    if (i < tasks.length - 1) {
      totalTime += switchCostMs;     // context switch
      switches++;
    }
  }

  console.log("Tasks completed: " + tasks.length);
  console.log("Context switches: " + switches);
  console.log("Useful work time: " + tasks.reduce((s, t) => s + t.duration, 0) + "ms");
  console.log("Switch overhead: " + (switches * switchCostMs) + "ms");
  console.log("Total time: " + totalTime + "ms");
  return totalTime;
}

// --- Event loop: zero context switches ---
function simulateEventLoop(tasks) {
  const totalTime = tasks.reduce((sum, t) => sum + t.duration, 0);
  console.log("Event loop total: " + totalTime + "ms (no switch overhead)");
  return totalTime;
}

const tasks = Array.from({ length: 100 }, () => ({ duration: 1 }));

console.log("=== Thread Pool ===");
simulateThreadPool(tasks, 0.5);
console.log("\n=== Event Loop ===");
simulateEventLoop(tasks);

Interactive Experiment

Try these exercises:

  • Modify the number of tasks from 100 to 10,000. How much overhead does the thread pool model accumulate?
  • Change the switch cost from 0.5ms to 5ms (simulating a heavier context switch). At what point does overhead exceed useful work?
  • Research how many context switches your actual system performs: on Linux run vmstat 1 and watch the cs column. On macOS try sudo dtrace -n 'sched:::on-cpu { @[execname] = count(); }'.

Quick Quiz

Coding Challenge

Calculate Total Scheduling Overhead

Write a function called `calculateOverhead` that takes an array of processes (each with `name` and `burstTime`) and a `quantum` and `switchCost`. Simulate round-robin scheduling and return an object with `totalTime` (including switches), `usefulWork` (just burst times), `overhead` (total switch cost), and `switches` (number of context switches). A context switch occurs every time the scheduler moves from one process to a different one.

Loading editor...

Real-World Usage

Context switching costs shape real engineering decisions:

  • Thread pools limit the number of threads to reduce switching. Java's Executors.newFixedThreadPool(n) typically uses n = number of CPU cores to minimize overhead.
  • Go's goroutines cost approximately 200 bytes of stack and switch in user space, enabling millions of concurrent goroutines vs thousands of OS threads.
  • Node.js avoids the problem entirely for I/O-bound work by using a single-threaded event loop. No threads means no context switches for application logic.
  • Database connection pools limit concurrent connections because each connection often maps to a thread. Too many connections means excessive context switching and slower overall throughput.

Connections