What Is an Abstract Syntax Tree (AST)?
An abstract syntax tree represents source code's grammatical structure as nodes a compiler, linter, or formatter can walk and transform programmatically.
An abstract syntax tree, or AST, is a tree-shaped representation of source code’s grammatical structure, where each node stands for a construct in the language — an expression, a function call, a loop, an assignment — rather than the raw text that spelled it out. Every compiler, interpreter, linter, formatter, and bundler that does anything more than pass text through builds an AST first, because a tree of meaningful nodes is something code can traverse, analyze, and rewrite; a string of characters isn’t.
Getting from text to tree
Turning source code into an AST happens in two stages. First, a lexer (or tokenizer) scans the raw characters and groups them into tokens — 2, +, 3, *, 4 from the text 2 + 3 * 4, discarding whitespace and comments along the way. Then a parser consumes that token stream and builds a tree according to the language’s grammar, encoding things the flat token list didn’t capture — like the fact that multiplication binds tighter than addition, so 3 * 4 needs to be evaluated as a unit before adding it to 2.
The result for 2 + 3 * 4 is a tree with an addition node at the root, 2 as its left child, and a multiplication node as its right child, which in turn has 3 and 4 as its own children:
(+)
/ \
2 (*)
/ \
3 4
Nothing here mentions whitespace, parentheses that only existed to guide precedence, or which order the tokens appeared in the source file — that’s what makes it abstract. A concrete syntax tree (or parse tree) keeps every grammatical detail, including punctuation and formatting-relevant tokens; an AST strips that down to the structure that actually matters for evaluating or transforming the code.
What actually consumes an AST
Once code is a tree, tools stop working with text and start working with structure:
- Compilers and interpreters walk the AST to generate machine code, bytecode, or to evaluate it directly. This is the tree a JIT compiler is compiling when it decides which parts of a running program are worth optimizing.
- Linters like ESLint traverse the tree looking for patterns that match known problems — a variable declared but never read, a comparison that’s always true — without ever caring how the code was formatted, because formatting isn’t in the tree. See ESLint vs Prettier for why that split exists: a linter reasons about the AST, a formatter reasons about the text.
- Transpilers like Babel and the TypeScript compiler parse source into an AST, transform nodes — stripping type annotations, rewriting newer syntax into an older target — and print a new source string back out from the modified tree.
- Bundlers such as those compared in esbuild vs Rollup vs webpack parse every module into an AST to figure out what each file imports and exports, which is how tree-shaking identifies and removes code nobody actually uses.
- Codemods — automated, large-scale code rewrites — parse a codebase into ASTs, apply a transformation to matching node patterns, and print the result back to disk, which is how a project can rename an API across thousands of files without a fragile find-and-replace.
Why “abstract” is doing real work in the name
The distinction between an AST and a full parse tree matters in practice, not just in theory. A parse tree for (2 + 3) and one for 2 + 3 differ, because the parentheses are literal tokens in the grammar. Their ASTs are identical, because once precedence is resolved into tree structure, the parentheses have done their job and can be discarded. This is also why an AST alone can’t perfectly reconstruct the original source — a code formatter that only had the AST would lose comments, exact whitespace, and any redundant parentheses a developer added for readability. Tools that need to preserve those, including most auto-formatters, keep additional information (like comment attachment and original token positions) alongside the tree rather than relying on the AST in isolation.
Where you’ve already touched one
If you’ve ever seen a source map connect a minified production error back to a specific line in your original file, that mapping exists because the bundler tracked node positions through every AST transformation from source to output. If you’ve used an editor’s “rename symbol” refactor and had it correctly skip a string that happens to contain the same text, that’s an AST-aware tool distinguishing an identifier node from a string literal node — something a plain text search can’t do.
The takeaway
An abstract syntax tree is what source code becomes the moment a tool needs to understand it rather than just display it: a structure of meaningful nodes instead of a string of characters, precedence and grouping already resolved, formatting and punctuation already discarded. Almost every piece of tooling a developer relies on daily — the compiler, the linter, the formatter, the bundler — is really just a program that builds an AST, walks it, and does something useful with what it finds.
Keep reading
The Lycoris Team · · 5 min read Functional Programming vs Object-Oriented Programming
Functional programming builds programs from pure functions and immutable data; OOP bundles state and behavior into objects. Key differences explained.
The Lycoris Team · · 5 min read What Is JIT Compilation? Just-In-Time Compiling Explained
Just-in-time (JIT) compilation translates code to native machine instructions while a program runs, trading startup time for peak execution speed.
The Lycoris Team · · 4 min read How Regular Expressions Work Under the Hood
Regular expressions are matched by finite automata or backtracking engines. How regex engines parse patterns, and why some patterns run slowly.