cswork / libraryOpen practice workspace

Articles

Linux Process Interview Questions: Zombies, Orphans, Signals, and Reaping

Practice Linux process interview questions about fork, waitpid, zombies, orphan adoption, signal handling, and safe supervisor shutdown in production.

A zombie process is already dead. Sending it SIGKILL cannot make it “more dead”; the missing operation is a parent's wait. That single distinction unlocks many Linux process interview follow-ups: why a parent sees SIGCHLD, why an orphan is adopted, how a supervisor should shut down children, and why a signal handler that reaps only one child can still leak process-table entries.

This is a technical practice guide grounded in Linux fork, wait, and signal manuals. The supervisor review below is an original exercise, not a candidate report of a named company's interview. For adjacent practice, Explain What Happens When a Linux Shell Runs ls makes you trace the parent's wait after a child exits.

The process lifecycle in one answer

fork() creates a child with its own PID and a separate address space. Both parent and child continue from the fork point, with different return values. The child may call execve() to run a new program; execve() replaces its program image but does not create a new process. When the child exits, the kernel retains a minimal record of its PID, termination status, and resource-usage information until the parent collects that status with wait, waitpid, or a related interface. That waiting-to-be-collected state is a zombie.

If the parent exits first, the child becomes an orphan and is adopted by a suitable reaper, commonly init or a process marked as a subreaper. An orphan may still be running; an adopted zombie still needs to be reaped. These words describe different facts: parent relationship versus exit state. The wait manual gives the precise zombie and adoption behavior.

Original code-review case: one SIGCHLD, three exits

Suppose a server starts three short-lived helpers. Its signal handler is conceptually:

void on_sigchld(int signal_number) {
    int status;
    waitpid(-1, &status, WNOHANG);
}

The code waits for at most one terminated child per handler invocation. Three children can finish close together while the process receives one observable SIGCHLD notification. Standard signals are not a reliable one-event-per-child queue. Two children can remain zombies until some later activity happens to trigger another reap. A correct design drains all waitable children when notified, rather than assuming the signal count equals the exit count.

In a simple single-threaded program, the pattern is to arrange a signal-safe notification to the main loop, then repeatedly call waitpid(-1, &status, WNOHANG) until it returns 0 (children exist but none have changed state) or -1 with ECHILD (there are no waitable children for that caller). Handle EINTR according to the program's loop and check errno rather than treating every -1 identically. Record which PID exited and whether it exited normally or from a signal using the WIFEXITED, WEXITSTATUS, and WIFSIGNALED macros. The exact threading and signal-delivery design requires care; a production runtime may supply a process supervisor or event-loop abstraction.

Why not put arbitrary logging, allocation, and container-map mutations inside the signal handler? The signal-safety manual limits which functions are async-signal-safe. A small handler that sets a suitable flag or writes to a self-pipe, with the main loop doing the richer bookkeeping, is easier to reason about. The interview point is not to memorize a clever one-liner. It is to connect coalesced notification, draining waitable children, and safe handler boundaries.

A zombie is not a running memory leak

The kernel keeps only limited accounting data for an exited child until its parent waits. The child is not still executing user code and does not retain its full active heap as though it were alive. But zombies do occupy process-table slots. A supervisor that creates helpers continuously and never reaps them can eventually make process creation fail. The wait man-page notes state that unreaped zombies consume a slot in the kernel process table.

This is why “kill the zombie” is the wrong remediation. The zombie no longer runs and cannot handle a new signal. Fix the parent process or its supervisor so it waits for children. If the parent itself exits, an adopter such as init or a subreaper will usually reap the orphaned zombie. Deliberately killing a healthy parent just to clear zombies is a disruptive operational shortcut, not a sound primary fix. In a container, check which process is PID 1 and whether it is designed to reap; an application launched as PID 1 may have inherited responsibilities its author did not expect.

If an interviewer asks whether SIGCHLD can be ignored, be precise. Linux has special semantics for explicitly setting SIGCHLD to SIG_IGN or using SA_NOCLDWAIT, under which children do not become zombies in the usual way. That is a design choice when the parent does not need exit status. It is not equivalent to installing an empty handler and hoping the kernel discards the records. If the supervisor needs to know success, failure, and retry eligibility, it should collect statuses rather than suppress them.

Orphans, init, and subreapers

A running child can outlive its original parent. The kernel reparents it; on Linux, the nearest living ancestor designated as a child subreaper can adopt it, otherwise an init process in the relevant PID namespace does. The adopter can later wait for it. This matters for daemonization, containers, and supervisors that launch grandchildren. A process that forks a shell which forks a worker might otherwise believe it has no child to wait for, while the worker becomes someone else's responsibility.

Do not confuse reparenting with an automatic restart. Adoption only assigns a parent relationship and a reaping path; it does not relaunch a crashed service. Likewise, execve() changes the program that a process runs but retains the process identity in ways a fresh fork() does not. In an interview, draw a PID timeline: parent 100 forks child 101; child 101 forks grandchild 102; 101 exits; 102 keeps running and is adopted by the nearest subreaper. When 102 exits, the adopter waits. That timeline is an illustrative model, not a transcript from a live system.

One follow-up is PID namespaces. A process can be PID 1 inside a namespace while having another PID outside it. A container's init or supervisor should handle signal forwarding and reaping for its namespace. Avoid promising that a container runtime always does this automatically; inspect the actual image entrypoint and runtime behavior.

Signals are requests, dispositions, and evidence

SIGTERM usually asks a process to terminate, giving it an opportunity to run an installed handler and clean up. SIGKILL cannot be caught, blocked, or ignored. SIGCHLD notifies a parent when a child changes state, but notification is not the same as collecting that child's status. The Linux signal reference documents standard signals and dispositions. An exit status also needs decoding: a child can exit with a normal code or be terminated by a signal.

In a supervisor interview, describe a graceful shutdown as a sequence. Stop accepting new tasks. Send SIGTERM to the exact child processes or managed process group you own. Wait for a bounded grace period while continuing to collect child status. Send SIGKILL only to children still running after the deadline. Continue reaping until there are no managed children left. The deadline is a product and operational policy; no universal number follows from Linux semantics.

Take care with process groups. A blanket kill(-pid, SIGTERM) targets a process group, not one PID, if the process has been arranged as a group leader; a wrong group ID can signal unrelated work. Record ownership when spawning, use appropriate group/session setup, and verify the target. If a child ignores SIGTERM, distinguish an application that chose to ignore it from one blocked in uninterruptible kernel sleep; simply sending more termination signals may not change the latter immediately.

How to inspect a live process tree

Start with ps or /proc to see PID, PPID, state, and command, then use pstree or a similar view to understand parentage. A Z state is consistent with a zombie; PPID identifies the process that should wait. If the parent is already gone, look for reparenting and namespace boundaries. Preserve timestamps, because PIDs can be recycled. “PID 102 was the culprit yesterday” is unsafe without time and process identity.

For a supervisor bug, inspect whether children exit faster than the parent reaps, whether SIGCHLD is blocked or mishandled, and whether the program starts grandchildren that it does not directly own. On Linux, strace can reveal wait4/waitid calls in a controlled investigation, though attaching a tracer changes timing. In a production incident, prefer existing logs and metrics first. Useful metrics include active child count, completed-but-unreaped count, spawn failures, restart attempts, and termination latency.

The diagnostic question “why can I no longer fork?” has other answers too: per-user task limits, cgroup pids.max, system thread limits, or memory pressure. A zombie buildup is one hypothesis, not a universal explanation. The fork manual lists several EAGAIN and ENOMEM paths. An interviewer will value a measured distinction more than a memorized story.

A worked answer rubric

PromptWeak shortcut and Stronger reasoning
“A zombie will not die after kill -9.”Weak shortcut: Try a stronger signal.Stronger reasoning: It has exited; inspect the parent and call wait.
“Only one SIGCHLD arrived for three children.”Weak shortcut: Expect three future signals.Stronger reasoning: Drain waitpid until no waitable child remains.
“The parent died but the child still runs.”Weak shortcut: The child must restart.Stronger reasoning: It is an orphan; identify its adopter or subreaper.
“Shutdown left helper processes.”Weak shortcut: Kill every PID found by name.Stronger reasoning: Stop new work, signal only owned children/groups, wait and reap.
“Fork returns EAGAIN.”Weak shortcut: Blame zombies alone.Stronger reasoning: Check task limits, cgroup PIDs, and process state.

This rubric is an original practice aid. It reveals the common thread: identify who owns the child lifecycle. A service manager can restart a crashed parent, but it still needs a correct reap path for children. A shell may wait for a foreground command, while a long-running supervisor must handle exits asynchronously and without losing statuses.

Final interview takeaway

Trace the PID and parent relationship through fork, optional exec, exit, and wait. A zombie is an exited child awaiting reaping; an orphan is a child whose original parent exited; a signal is neither a replacement for wait nor proof of who owns a grandchild. Practice the shell-running-ls question, then explain how your answer changes for a supervisor with hundreds of concurrent children.

Sources and Further Reading

Research checked September 28, 2026. Process supervision differs across containers, service managers, runtimes, and PID namespaces; inspect the actual environment.