A child process can finish executing without disappearing immediately. Another child can continue executing even after the process that created it has disappeared.
These two situations produce two memorable Unix terms:
The names sound similar, but the underlying situations are almost opposites. A zombie is dead but still has a process-table record. An orphan is alive but has lost its original parent.
Understanding the distinction matters in real systems. A backend service that launches helpers without collecting them can slowly fill its process table with zombies. A container whose main process does not act as a proper parent can inherit descendants and fail to reap them.
From the child's point of view, termination happens when it calls _exit, returns from main, or is terminated by the operating system. Execution stops and the kernel releases most of the process's resources.
From the parent's point of view, one piece of work remains: learning how the child terminated.
This creates a two-stage lifecycle:
Nothing the child can do moves it out of the zombie state, because the only transition out depends on the parent.
The call that collects the child's status is commonly described as reaping the child.
This design prevents a race. A fast child might finish before its parent reaches waitpid(). Instead of discarding the result, the kernel retains it. When the parent eventually waits, the status is still available and the call can return immediately.
A zombie is a child process that has terminated but has not yet been reaped by its parent.
The zombie does not execute instructions. It cannot resume its old work, and it is not waiting for CPU time. The kernel has already released resources such as its user-space memory and open file descriptors.
What remains is a small amount of process bookkeeping needed by the parent, including:
This is why describing a zombie as a “process that is still running” is incorrect. Its computation is over; its termination record is still present.
On Linux, process inspection tools report a zombie with state Z and often display <defunct> in the command column.
The PPID is especially important: it identifies the parent responsible for collecting that zombie.
Suppose a parent starts a child and then performs other work:
Parent and child run independently after the fork. The child can finish long before the parent asks, and the kernel holds the result in the meantime.
The exit code 42 must remain available until the parent asks for it. The kernel therefore preserves a compact termination record.
That record also tells the parent whether the child:
The wait-status macros interpret the encoded status returned by wait() or waitpid():
Once a successful wait consumes the termination record, the child is reaped and its process-table entry can be removed.
The following program deliberately delays waitpid() so that the child remains a zombie long enough to inspect.
Compile and run it on a Unix-like system:
While the parent is sleeping, use the printed child PID in another terminal:
The child should appear with state Z. After the parent calls waitpid(), running the same command produces no entry because that PID is no longer present.
The twenty-second delay is useful for demonstration but is not a valid reaping strategy. Real programs arrange to collect children promptly.
kill Cannot Fix a ZombieSending a termination request to a zombie cannot make it “more terminated.” The process has already stopped executing, so there is no running program to receive or act on the request.
The missing action must come from the parent:
Signalling the zombie child is the wrong target, because it has already terminated. The action that resolves it belongs to the parent, which must call wait or waitpid.
If the defective parent terminates, the zombie is reparented to a process that is expected to reap adopted children. That often makes the zombie disappear, but terminating a production parent is a recovery action, not a fix for faulty child management.
The durable fix is to correct the parent so every child it creates is eventually collected.
One zombie consumes little memory compared with a running process. A zombie no longer owns its former heap, stack, or open sockets.
The problem is the retained kernel bookkeeping. Every unreaped zombie occupies a process-table slot and keeps its PID in use. If a faulty server continuously launches short-lived children, the number of zombies can grow without bound.
Eventually, the machine, user account, service, or container can reach a process-count limit. New calls to fork() or similar creation operations may then fail. The visible symptom might be “cannot create process,” even though the true cause is an old reaping bug.
This pattern appears in backend systems that launch:
“Fire and forget” is not a complete process-lifecycle policy. If a service creates a child, it must either collect that child or deliberately transfer responsibility to a supervisor that will.
When a parent cannot proceed until a particular child finishes, blocking waitpid() is straightforward:
Passing a positive PID selects that specific child. With options set to 0, the call blocks if the child is still running and returns immediately if the child has already become waitable.
Each successful call collects one child's status. Waiting once is therefore insufficient when a parent has created several children.
A server often has other work to do and cannot pause until an arbitrary child exits. WNOHANG lets waitpid() check for completed children without waiting for a living one.
The PID argument -1 means “select any child.” The loop is essential because several children may terminate before the parent gets an opportunity to run. A single waitpid() call would reap only one of them.
A production event loop can call this drain function whenever it needs to process completed children. It should also associate each PID with the operation that launched it, record success or failure, and remove the child from its own tracking structures.
There are configurations in which the operating system discards child termination records automatically. Those configurations also change whether later wait calls can retrieve status. Explicit reaping is usually the clearest design when exit results matter.
Ordinary wait() and waitpid() calls operate on the caller's child processes. An unrelated process cannot repair a zombie by waiting for it.
This ownership rule is valuable. Exit status goes to the component that created and managed the work, rather than being consumed unpredictably by another process.
It also makes diagnosis direct:
Z.PPID.If many zombies share the same parent PID, that parent is the first place to investigate.
An orphan is a process whose parent terminates while the child is still running.
The operating system does not terminate the child merely because its original parent is gone. Instead, it changes the child's parent relationship so that another process becomes responsible for it.
| Before parent exits | After parent exits | |
|---|---|---|
| Child's parent | The original parent | An adopting process |
| Child's PPID | The original PID | The adopter's PID |
| Child's execution | Still running | Still running, undisturbed |
The child keeps its own PID, address space, open descriptors, and execution state. Its parent PID changes.
Traditionally, orphaned processes are described as being adopted by init, the system's PID 1 process. That is a useful starting model, but modern Linux has two important refinements:
The accurate rule is that Linux reparents an orphan to an appropriate living subreaper or init process for its process hierarchy.
This program lets the original parent exit while its child remains alive:
Compile and run it:
The pipe ensures that the parent stays alive until the child has printed its original parent PID. After the delay, the original parent is gone and getppid() reports the adopter's PID.
The exact number depends on the environment. On a traditional host it may be 1; under a shell, service manager, test harness, or container runtime, a subreaper may adopt the child instead.
The clearest comparison is based on two questions: Is the child still running, and is the original parent still alive?
| Condition | Child executing? | Original parent alive? | What must happen next? |
|---|---|---|---|
| Normal running child | Yes | Yes | Parent continues managing it |
| Zombie | No | Usually yes | Parent must collect its status |
| Orphan | Yes | No | Kernel reparents it to an adopter |
| Reaped child | No | Irrelevant | No process entry remains |
An orphan does not automatically become a zombie. It normally continues running under its new parent.
When that orphan eventually terminates, it can briefly enter the zombie state like any other child. Its adopting parent is then responsible for reaping it.
A zombie can also become orphaned in a bookkeeping sense if its parent terminates without reaping it. The kernel reparents the termination record so that an adopter can collect it.
Loading simulation...
Linux allows a process to mark itself as a child subreaper. If a descendant later becomes orphaned, the nearest living ancestor with this role adopts it.
This is useful for service supervisors and process-management frameworks:
Without the subreaper setting, the helper would be adopted somewhere else entirely, becoming the responsibility of a process that knows nothing about it.
Without the subreaper role, the helper could be reparented farther up the process hierarchy. With it, the supervisor can observe and collect descendants whose immediate parents disappear.
Subreaping does not eliminate the need for wait. It changes which process receives the responsibility.
A container often has its own PID namespace. The first process in that namespace appears as PID 1 and serves as its init process.
If an application runs directly as container PID 1, it may adopt orphaned descendants. It must collect them when they terminate. An application that assumes “some system init process will handle this” can therefore leak zombies inside the container.
This commonly happens when:
Container deployments often use a small init or a process supervisor to forward lifecycle events and reap adopted children. Whether that extra component is necessary depends on the process tree and whether the main application correctly manages every descendant it can inherit.
The essential invariant is simple:
Every terminated child must eventually have a living parent or adopter collect its status.
On Linux, this command provides a useful system-wide view:
Look for Z in the STATE column. To filter the output:
For each result, inspect the parent:
Elapsed time helps distinguish a transient zombie from a persistent leak. A zombie can exist briefly in a healthy program between child termination and the parent's next reaping opportunity. A steadily growing count or a zombie that remains for a long time points to broken lifecycle handling.
Useful questions during diagnosis include:
waitpid() on every creation path?Restarting the parent may clear its existing zombies through reparenting, but monitoring after the restart reveals whether the underlying leak remains.
wait() or waitpid() removes the zombie's remaining process-table record.5 quizzes