When a program's code shares the structure of its data, so it can read and write itself. The thing that reads is the thing that runs.
Here is one expression, (+ 1 (* 2 3)), and the trick is that it is two things at once. Read as data, it is simply a list whose first element happens to be the symbol + and whose last element is another list — boxes inside boxes, nothing running, fully editable. Click an atom and you have rewritten the program by editing a list. Read as code, those identical parentheses evaluate from the inside out, each call collapsing to a value until only a number is left. The source text never changes between the two readings, because there is nothing to change: in a homoiconic language the program and the data are the same object.
The word joins the Greek for same with the Greek for image: code and data wearing a single form. A homoiconic language writes its programs in the very same structure it uses for its values, so the distinction between the thing that runs and the thing it operates on quietly dissolves.
John McCarthy's Lisp represents code as S-expressions — nested lists — the exact same lists the language manipulates as data. A program is a list; a list is data; therefore a program is data a program can build. The founding trick of the field's second-oldest language is this collapse.
In Lisp the written syntax is the tree: there is no separate grammar hiding an AST, the parentheses are the AST. quote takes code and hands it back as data; eval takes data and runs it as code. The boundary is a door you can walk through in either direction at will.
Homoiconicity's payoff is the macro: a program that receives unevaluated code as data, rewrites it, and returns new code to be run. Lisp had it first; Julia, Elixir, Clojure, and Rust's macro systems chase the same power. Metaprogramming is just editing the territory with the map's own hands.
The term itself comes from the TRAC language of the 1960s; Rebol built a dialect-making language around the principle; Smalltalk reflected on itself as living objects. Each arrived, by a different road, at the same realization — that a system powerful enough should be able to hold itself in its own hands.
Linters, formatters, codemods, compilers, and refactoring engines all treat programs as data: parse to a tree, transform the tree, print it back. Most languages are not homoiconic, yet the workflow smuggles the idea in through the AST. The map and the territory turn out to be the same paper after all.
A language model emits a program as a string; a tool parses that string into an AST and rewrites it; the result is run, and may emit more code in turn. Generated, parsed, transformed, executed, regenerated — the loop is homoiconicity's spirit at industrial scale. The thing that reads is, once again, the thing that runs.