Harshit Batra
← Back to Resources

Operating System

fork() Doesn't Do What Most Students Think It Does

The fastest way to actually test fork() understanding: put code on the screen and ask "how many times does this print, and what values?" Every explanation of fork sounds reasonable until someone has to trace actual output.

The mental model that's actually correct

fork() creates a near-identical copy of the calling process. After it runs, you have two separate, independent processes, each with its own copy of the entire memory image (code, heap, stack, data). A change to a variable in one is invisible to the other — which is exactly why IPC has to exist.

The one thing that differs between the two copies is the return value of fork() itself:

  • The child gets a return value of 0.
  • The parent gets a return value equal to the child's PID.
  • If fork failed, the return value is negative.

Everything else people find confusing about fork is really "what happens when two independent processes both start running from the same point in the same code."

The exercise that actually teaches it

int ret = fork();
int x = 1;

if (ret == 0) {
    printf("I am child\n");
    x = x + 1;
    printf("Child x = %d\n", x);
} else if (ret > 0) {
    printf("I am parent\n");
    x = x - 1;
    printf("Parent x = %d\n", x);
}

Trace it as two separate executions. Parent and child each have their own copy of x, because the memory image was copied at the moment of fork(). The child increments its copy → prints Child x = 2. The parent decrements its own, separate copy → prints Parent x = 0.

Where it gets genuinely tricky: multiple forks

fork();
fork();
print("hello");

Walk it as a tree, not a list. The first fork() creates 2 processes. Each of those two then hits the second fork() independently and creates its own child — 4 processes total, not 3. Each of the 4 then runs print("hello") once. Total: 4 processes, "hello" printed 4 times.

flowchart TD
    Start["1 process"]
    Start -->|"fork() #1"| A["continues"]
    Start -->|"fork() #1"| B["child"]
    A -->|"fork() #2"| A1["continues"]
    A -->|"fork() #2"| A2["child"]
    B -->|"fork() #2"| B1["continues"]
    B -->|"fork() #2"| B2["child"]
    A1 --> H1(["hello"])
    A2 --> H2(["hello"])
    B1 --> H3(["hello"])
    B2 --> H4(["hello"])

GATE 2012 pushes this further — three forks in a row, asking for the number of child processes specifically:

fork();
fork();
fork();

Three forks doubles the process count three times: 1 → 2 → 4 → 8 total processes. Children = 8 total − 1 original parent = 7. The common mistake: counting fork() calls (3) instead of tracing the branching tree — process count grows exponentially, not linearly, because every existing process hits every subsequent fork call.

exec() and wait() — what fork() alone doesn't give you

  • exec() replaces a process's memory image with a different executable entirely. This is how a shell runs commands — not by containing ls or cat itself, but by forking a child and having that child exec() the real binary.
  • wait() lets a parent block until a specific child terminates, and "reaps" that child's remaining process-table entry once it does.

Zombies and orphans

Zombie process: when a child calls exit(), its memory is freed, but its process-table entry (PID, exit status) stays until the parent calls wait() and reads it. A parent that forks many children and never calls wait() accumulates zombie entries until the process table fills up — a real, common bug, not a theoretical one.

Orphan process: the opposite ordering — the parent dies while the child is still running. The child gets reparented to init (PID 1), which adopts it and reaps it when it eventually terminates.

Quick recall

  • fork() creates an independent copy: child gets return value 0, parent gets the child's PID
  • N forks in a row → 2^N total processes — trace the branching tree, don't just count fork() calls
  • exec() replaces a process's memory image; wait() blocks until a child exits and reaps its entry
  • Zombie = child exited, parent hasn't called wait() yet. Orphan = parent died first, child reparented to init

Quick quiz

  • 1. fork(); fork(); print("hello"); — how many times does "hello" print in total?

  • 2. What return value does the child process get from fork()?

  • 3. What's the actual difference between a zombie and an orphan process?