Visual walkthrough — Preprocessor directives — #define, #ifdef, #ifndef, #include guards
Step 1 — What #include actually does
WHAT. A C program is usually split across many files. To share a piece of text (like a struct definition) between files, you put it in a header file (a .h file) and write #include "types.h" wherever you want it.
WHY this tool and not copy-paste by hand? Because the preprocessor — the text-editing robot that runs before the compiler — does the pasting for you, automatically, every build. You write the text once; #include stamps a fresh copy wherever it appears. No tool "understands" the C yet; this is pure find-and-glue.
PICTURE. Look at the figure. On the left is a small file types.h holding one struct. The blue arrow shows what #include "types.h" becomes: the preprocessor deletes the #include line and drops the file's full text in its place.

Term by term, the transformation is:
Step 2 — Files include other files (the diamond forms)
WHAT. Headers include headers. Suppose a.h needs Point, so a.h writes #include "types.h". Likewise b.h needs Point, so b.h also writes #include "types.h". Now your main.c wants both features, so it includes both:
#include "a.h"
#include "b.h"WHY care? Because each #include is an independent paste. The preprocessor doesn't remember it already handled types.h for a.h — when it reaches b.h, it pastes types.h again.
PICTURE. The figure is a diamond: main.c at the top, two paths (a.h and b.h) fanning out, both pointing down to the same types.h. Two arrows arrive at types.h — that's the warning sign: two routes, one file, two pastes coming.

Step 3 — Watch the double-paste happen
WHAT. Let's expand main.c literally. The preprocessor works top to bottom. It sees #include "a.h", pastes a.h's text — and inside that text is #include "types.h", which it pastes too. Then it reaches #include "b.h" and does the whole thing over again.
WHY show the raw expansion? Because the bug is invisible in the source, but obvious once you see the pasted result. The final translation unit — the fully-expanded text handed to the compiler — contains the struct line twice.
PICTURE. The figure stacks the resulting translation unit. The first pink block is struct Point { int x, y; }; from the a.h path. The second pink block is the identical line from the b.h path. Two identical blocks, one file.

Step 4 — Why the compiler screams "redefinition"
WHAT. The compiler now reads the translation unit and meets struct Point { int x, y; }; a second time. Defining the same struct twice in one translation unit is illegal C — the compiler stops with an error like redefinition of 'struct Point'.
WHY is duplication illegal? Because a definition tells the compiler the complete shape of a type (its members, its size). Two definitions could disagree; even when identical, the language forbids a second full definition in one unit to keep meaning unambiguous.
PICTURE. The figure shows the compiler reading down the two blocks: the first block gets a green check ("Point learned ✓"); the second gets a pink cross and a speech bubble "I already know Point — this is a redefinition!"

Step 5 — The idea: a "have I been here?" flag
WHAT. We need the file to notice: "the second time you paste me, do nothing." The preprocessor can't remember on its own — but it can remember one thing: whether a name is currently defined.
WHY this tool — #define as a flag? #define NAME (with no value) just switches a name to the state "defined." It uses no memory and no type — it's a light switch the preprocessor can check later with #ifdef / #ifndef. That "is this name defined?" question is exactly the memory we lacked in Step 2.
PICTURE. The figure shows a light switch labelled TYPES_H. Left panel: OFF ("not defined"). Right panel: ON ("defined"). Below each, the preprocessor asks #ifndef TYPES_H ("is it not defined?") and the answer flips from YES → keep to NO → skip.

Step 6 — Wrap the header in the guard
WHAT. Put the whole header body between three lines: check the flag, set the flag, ...body..., close.
/* types.h */
#ifndef TYPES_H /* (1) is the flag OFF? */
#define TYPES_H /* (2) turn it ON now */
struct Point { int x, y; }; /* (3) the body */
#endif /* (4) close the check */WHY in this exact order? Line (2) must come immediately after line (1) so the flag is set before any second paste can happen. The moment the header is entered once, TYPES_H is on — so any later paste finds the switch already on and is skipped.
PICTURE. The figure annotates each of the four lines with a coloured tag: yellow "(1) ask", blue "(2) set switch", white "(3) real content", pink "(4) end". An arrow loops from the body back up to show the switch guarding it.

Step 7 — Replay the diamond, now guarded
WHAT. Re-run Step 3 with the guard in place.
- First paste (via
a.h):TYPES_His OFF →#ifndefis true → preprocessor turns it ON and keeps thestruct. ✔ body appears once. - Second paste (via
b.h):TYPES_His now ON →#ifndefis false → preprocessor deletes everything up to#endif. ✘ body skipped.
WHY this fixes it. The struct now lands in the translation unit exactly once, so Step 4's compiler check passes.
PICTURE. The figure repeats the two-block stack from Step 3, but the second block is greyed-out and crossed, with a caption "flag already ON → skipped." Only one live pink block remains.

Step 8 — Edge cases the guard must survive
WHAT. Three scenarios that trip beginners:
- Two headers, same tag. If
a.handb.hboth use#ifndef HEADER_H, whichever is pasted first turns the switch ON and blocks the other entirely — your second header silently vanishes. Fix: make each tag unique, e.g. derive it from the filename (A_H,B_H). - A file that is never double-included. The guard costs nothing here —
#ifndefis true once, false never. It's harmless insurance; always guard headers anyway. #pragma onceinstead. One line at the top does the same job by tracking the file's identity, not a name. It can't clash with a mistyped tag — but it's compiler-specific, whereas the#ifndeftrio is standard C everywhere.
WHY separate steps? Because "it compiled on my file" hides case 1 completely; you only discover it when a second file mysteriously loses its contents.
PICTURE. The figure splits into three chalk panels: (left) two files fighting over one tag HEADER_H with the loser greyed out; (middle) a lone unincluded-twice file with a "no cost" tick; (right) #pragma once shown as a single-line stamp with a "compiler-specific" caveat.

The one-picture summary
The whole story in one frame: the diamond (main.c → a.h/b.h → types.h), the two paste attempts, the guard switch, and the outcome — first paste kept, second paste skipped, exactly one struct reaching the compiler.

Recall Feynman: retell the whole walkthrough
Imagine a robot that copies pages into a big binder before the teacher reads it. When it sees "insert page types," it grabs the types page and glues it in. Your table of contents has two routes that both say "insert page types" — through folder a and through folder b — so the robot glues that same page in twice. The teacher opens the binder, sees the Point definition once ("got it"), then sees it again ("wait, you already told me this!") and refuses to continue.
The trick: on the types page, at the very top, we write "if the TYPES sticker isn't on the cover yet, stick it on and keep this page — otherwise throw this copy away." The first time, no sticker exists, so the robot sticks it and glues the page. The second time, the sticker is already there, so the robot bins the duplicate. The teacher now sees Point exactly once and is happy. Just make sure every page uses its own sticker name, or two pages will steal each other's slot.
Recall Quick self-check
Why does an unguarded shared header cause "redefinition"? ::: Each #include is an independent paste, so a diamond include path pastes the file's body twice into one translation unit, and a repeated definition is illegal C.
What does #define TYPES_H (no value) do inside a guard? ::: Flips a nameless flag to "defined" so a later #ifndef TYPES_H becomes false and skips the second paste.
Why must the #define come right after the #ifndef? ::: So the flag is set on the first entry, before any second paste can occur.
Why must guard tags be unique per file? ::: A shared tag lets whichever header is included first block the other entirely.
Related: Header files and translation units · Compilation pipeline — preprocess, compile, assemble, link · Macros vs inline functions · Conditional compilation for cross-platform code · Build flags -D and the command line · parent Preprocessor directives