the file-descriptor table
Picture the coat-check counter again, but now look behind it: a row of numbered pegs. Peg 0, peg 1, peg 2, and so on. Your ticket number tells the clerk which peg to walk to, and on each peg hangs a tag describing what is actually there. The file-descriptor table is that row of pegs - one private row per running program.
Concretely, every process has its own array of file-descriptor slots, indexed by the small integers we call descriptors. Slot 3 holds whatever fd 3 currently refers to. Each slot does not hold the file directly; it points to a kernel structure (often called the open file description) that tracks one open instance of a file - including its current file offset and the flags it was opened with. That structure in turn points to the file's inode, the kernel's record of the actual file. So there are layers: your fd indexes the table, the table entry points at an open-file structure, and that points at the file. When open() succeeds, the kernel picks the lowest free slot and returns its number; that lowest-free rule is exactly why redirection tricks work.
Why it matters: this table is what makes descriptors per-process and cheap. When a program forks a child, the child gets a copy of the table, so it inherits the parent's open files - which is how a shell wires up pipelines. dup() and dup2() simply make a second slot point at the same open-file structure as an existing one. Understanding the table - fd, then open-file description, then inode - explains why two descriptors can share one file offset while two separate open() calls on the same path do not.
After a fresh start, the table has slot 0 -> stdin, slot 1 -> stdout, slot 2 -> stderr. The next open() takes slot 3 (the lowest free one). If you close(0) first, that same open() takes slot 0 instead.
The lowest-free-slot rule is the lever redirection pulls on.
Two descriptors made by dup() share one open-file description, so they share a single file offset; two independent open() calls on the same path get separate offsets. Confusing these two cases is a classic source of surprising read/write positions.