a zombie process
The name sounds dramatic, but a zombie is a quiet, harmless-looking thing - until they pile up. A zombie process is one that has already finished running and terminated, but whose parent has not yet collected its exit status with wait(). The process is dead in every meaningful sense: its memory is gone, it runs no code, it uses no CPU. The only thing left is a tiny tombstone in the kernel's process table - the PID and the exit status - kept around so the parent can still find out how the child ended. It is dead but not yet buried.
Here is the precise mechanism. When a child terminates, the kernel cannot fully delete it, because the parent might still want to call wait() and read 'did my child succeed?'. So the kernel keeps that minimal record and marks the child as a zombie (shown as state Z, or 'defunct', in ps). The instant the parent calls wait() or waitpid(), the kernel hands over the status and erases the record - the zombie is reaped and vanishes. A zombie therefore exists only in the window between 'the child terminated' and 'the parent reaped it'. If the parent reaps promptly, zombies are momentary and you will rarely even see one. They become a problem only when a parent keeps creating children and never waits: each finished child stays a zombie, and because each one still occupies a PID-table slot, a buggy long-running parent can slowly exhaust the system's PIDs.
Why it matters: zombies are a classic symptom of a missing wait(), not a danger in themselves - you cannot kill a zombie with a signal (it is already dead; kill -9 does nothing). The real fix is to make the parent reap its children, or to arrange for them to be auto-reaped. There is one tidy escape: if the parent itself dies, its zombie children are reparented to init (PID 1), and init reaps them - so a zombie's other way out is for its negligent parent to exit. Recognizing a row of <defunct> entries in ps as 'a parent that forgot to wait' is a core debugging skill.
A parent calls fork() in a loop to spawn workers but never calls wait(). In ps you see lines like '1888 pts/0 Z+ 0:00 [worker] <defunct>'. Each finished worker is a zombie holding a PID slot. kill -9 1888 does nothing; the cure is to make the parent wait() (or have the parent exit, sending them to init).
A <defunct> entry is a terminated child waiting to be reaped - you cannot kill what is already dead.
A zombie uses no CPU and almost no memory; the cost is a PID-table slot, not resources. The fix is never kill -9 (it is already dead) - it is making the parent reap, or letting the parent exit so init reaps.