cf.completefrontend
Open lab My progress
THE TYPESCRIPT FIELD GUIDE

Inside the TypeScript compiler

Follow a line of TypeScript through the scanner, parser, binder, checker, and emitter, and see how tsc and your editor share one compiler.

By the end, you can
  • 01
    Name the five partsSay what the scanner, parser, binder, checker, and emitter each take in and hand on.
  • 02
    Read the compiler's workRead tokens, a syntax tree, and scopes, and tell which part of the compiler reported an error.
  • 03
    Explain tsc and the editorDescribe a Program, and how tsc and your editor's language service use the same compiler in different ways.

Your code's trip through the compiler

Every lesson so far has shown you what TypeScript says about your code. This lesson shows how it works that out. Here is a small file. Point at any name to see its type:

A small cart filePop out in the code editor (opens in a new tab)TypeScript
const price: number = 3;const quantity = 2;const total = price * quantity;//    ^?console.log(total);

Line 1 makes price, a number. Line 2 makes quantity. Line 3 multiplies them, and the query on line 4 shows const total: number. Line 5 prints 6.

To get from this text to that answer, the TypeScript compiler (the program behind tsc and your editor) passes the file through five parts, one after another:

  1. The scanner reads the characters and groups them into tokens: const, price, :, number, and so on.
  2. The parser fits the tokens together into a syntax tree: line 1 is a variable declaration with a name, a type, and a value.
  3. The binder writes down every name the code declares, like price and total, and which part of the code each one belongs to.
  4. The checker works out the type of every name and value, and reports the errors.
  5. The emitter writes the JavaScript, without the types.
Your code's text goes through five parts in order: the scanner makes tokens, the parser makes a syntax tree, the binder makes symbols, the checker works out types and errors, and the emitter writes JavaScript.const price: number = 3;your code, as textscannertokensparsersyntax treebindersymbolscheckertypes, errorsemitterJavaScripttsc and your editor run the same five parts
Five parts, one after another. Each one takes what the part before it made and adds something: tokens, then a tree, then symbols, then types and errors, and last the JavaScript.

You met the two big jobs, checking and emitting, in How TypeScript works. This lesson opens them up. Knowing the parts helps you read errors (which part is complaining?), understand why your editor answers so fast, and know what tools like linters and bundlers borrow from the compiler.

Real-life analogyA newspaper story on its way to print

A story passes through many hands before it reaches the paper. A typist turns the notes into words. An editor makes sure the words form sentences. A clerk lists every person the story mentions, so "she" on page two can be traced to "Asha" on page one. A fact checker checks each claim. Only then does the press print it.

In real life: A typist splits the reporter's notes into words
In TypeScript: The scanner splits your code into tokens
In real life: An editor checks that the words make proper sentences
In TypeScript: The parser builds a syntax tree, or reports a syntax error
In real life: A clerk lists every person the story names
In TypeScript: The binder makes a symbol for every declared name
In real life: A fact checker checks each claim against the list
In TypeScript: The checker checks each value against its type
In real life: The printing press prints the story
In TypeScript: The emitter writes the JavaScript
The main idea

The TypeScript compiler is five parts in a row. The scanner turns text into tokens, the parser turns tokens into a syntax tree, the binder gives every name a symbol, the checker works out types and reports errors, and the emitter writes JavaScript. tsc, your editor, and the checker on this page all run the same parts.

The scanner and the parser: from text to a tree

To the compiler, your file starts as one long string of characters. The scanner reads it from left to right and cuts it into tokens, the smallest pieces that mean something: a keyword like const, a name like price, a number like 3, or punctuation like : and ;. Spaces, line breaks, and comments separate tokens but aren't tokens themselves. The compiler calls them trivia. JavaScript engines read your code the same way (Lexical grammar covers JavaScript's tokens).

The parser asks the scanner for one token at a time and fits them into a tree. Each piece of the tree is a node, and each node has a kind, like VariableStatement or NumericLiteral (a number written in the code). This tree is called the abstract syntax tree, or AST. "Abstract" means it keeps the meaning and drops details like spaces and the ;. JavaScript engines build one too; Parsing & abstract syntax trees shows theirs.

The playground below runs the real compiler on one line you type. Pick 1. Scanner to see the tokens, then 2. Parser to see the tree. The other buttons are for the next sections, but try them too.

Playground: one line through the compiler
The line you typedPop out in the code editor (opens in a new tab)TypeScript
const price: number = 3;
Tokens the scanner read…

Loading the compiler…

Try it yourself
Tokens the scanner read

The scanner reads the characters from left to right and groups them into tokens: a keyword, a name, a colon, a number. Spaces and comments are skipped. Seven tokens, one statement in the tree, one name in the file's scope, and no errors. The emitter writes the same line without : number.

The real TypeScript compiler, running in this page, makes every list here. Change the line or pick a part. Reset goes back to the first line and the scanner.

For const price: number = 3;, the scanner reads seven tokens: ConstKeyword, Identifier (a name), ColonToken, NumberKeyword, EqualsToken, NumericLiteral, and SemicolonToken. The parser turns them into the tree below. The const keyword doesn't get a node of its own: the parser writes "this is a const" on the declaration list instead.

A syntax tree for const price: number = 3. VariableStatement holds VariableDeclarationList, which holds VariableDeclaration, which has three parts: Identifier price, NumberKeyword, and NumericLiteral 3.VariableStatementVariableDeclarationListVariableDeclarationIdentifier: priceNumberKeywordNumericLiteral: 3the namethe typethe value
The tree for const price: number = 3;. The statement holds a list of declarations, the list holds one declaration, and the declaration has three parts: the name, the type, and the value.

When the tokens don't fit, the parser reports a syntax error. Here is one, on purpose:

An if with a missing bracket (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
const cart = ["Tea", "Cake"];if (cart.length > 0 {  console.log("Cart has items");}

Line 1 makes a cart with two items. Line 2 starts an if but never closes the bracket around its condition. The parser says ')' expected: it expected a ) and found {. Notice the error's number, ts(1005). Errors numbered in the 1000s come mostly from the scanner and the parser.

The parser doesn't give up at the first mistake. It recovers: it acts as if the ) were there and keeps building, so the rest of the file still gets a tree, and the later parts still run. You can see this in what the emitter writes for the code above. The bracket you forgot is in it:

What the emitter writes: the parser filled in the bracketPop out in the code editor (opens in a new tab)JavaScript
const cart = ["Tea", "Cake"];
if (cart.length > 0) {
    console.log("Cart has items");
}

Line 2 now reads if (cart.length > 0) {, and the code prints Cart has items. Don't rely on this: the parser's guess is only a guess, and a wrong guess can cause confusing errors further down. Fix syntax errors first.

The binder: a symbol for every name

A tree tells the compiler what each line looks like, but not what the names mean. If a file says discount in three places, are they the same discount? The binder answers that. It walks the tree once and, for every name the code declares, makes a symbol: one record for that name. Later, the checker connects each use of the name to its symbol.

It puts each symbol in a scope: the part of the code where the name exists. The file is a scope, each function is a scope, and a block in { } is a scope for let and const (the Scope lesson covers the JavaScript rules the binder follows). Here are two scopes with a discount each:

Two discounts in two scopesPop out in the code editor (opens in a new tab)TypeScript
const discount = 10; function finalPrice(price: number) {  const discount = 2;  return price - discount;  //             ^?} console.log(finalPrice(20));console.log(discount);

Line 1 declares a discount in the file's scope. Line 3 declares finalPrice, also in the file's scope, and its parameter price, which belongs to the function. Line 4 declares a second discount, in the function's scope. The binder gives each discount its own symbol.

Line 5 uses discount. To find out which one, the compiler looks in the closest scope first, the function, and finds the one from line 4. The query on line 6 shows const discount: 2: the inner one, with its exact value as its type because a const can never change. Line 9 prints 18 (20 minus 2), and line 10, outside the function, prints the outer 10.

A window for the file's scope holds the names discount and finalPrice. Inside it, a window for the finalPrice function's scope holds price and discount. A note says line 5 looks in the closest scope first and finds the inner discount, 2.the filediscount: 10finalPricefinalPricepricediscount: 2line 5finds this onethe closest scope wins
The binder makes one table of names for the file and one for finalPrice. Line 5 looks in the closest scope first, so its discount is the inner one, 2.

The binder also notices when one scope declares the same name twice. This is an error on purpose:

One name declared twice (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
let total = 0;let total = 5;console.log(total);

Lines 1 and 2 both declare let total in the file's scope. When the binder adds the second one to the file's table of names, it finds the first one already there and reports Cannot redeclare block-scoped variable 'total' at both lines. "Block-scoped" means a let or const. JavaScript agrees: running this code stops with a SyntaxError.

The binder never asks what type anything is. It only records which names exist and where. That makes it fast, and it means the checker, the next part, never has to search the whole tree for a name.

The checker and the emitter

The checker is the biggest part of the compiler, and it does the work you have seen in every lesson. For each name, it follows the symbol to its declaration and works out its type. For each value, it compares the value's type with the type the code says it should have:

The checker works out types and finds a mismatch (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
type CartItem = { name: string; price: number }; const tea: CartItem = { name: "Tea", price: 3 };const label = tea.name.toUpperCase();//    ^?const cost: string = tea.price;

Line 1 names a type, CartItem. Line 3 makes tea a CartItem, and the checker confirms the object has a text name and a number price. Line 4 calls toUpperCase on tea.name: the checker follows tea to its symbol, finds the type CartItem, finds that name is a string, and finds that strings have a toUpperCase that gives back a string. The query on line 5 shows the result: const label: string.

Line 6 says cost must be a string and gives it tea.price, a number. The checker reports Type 'number' is not assignable to type 'string', on purpose here: a number is going where text has to go.

The checker is lazy: it works out a type only when someone asks for it. When tsc checks a file, it asks about everything. When you point at one name in your editor, it asks about that one name, and the answer comes back fast.

The emitter comes last. It walks the syntax tree and prints it back out as JavaScript, leaving out every type, interface, and type alias:

A file before the emitterPop out in the code editor (opens in a new tab)TypeScript
type CartItem = { name: string; price: number }; // Tea is the cheapest item.const tea: CartItem = { name: "Tea", price: 3 };console.log(tea.price * 2);
What the emitter writesPop out in the code editor (opens in a new tab)JavaScript
// Tea is the cheapest item.
const tea = { name: "Tea", price: 3 };
console.log(tea.price * 2);

The type alias on line 1 is gone, and so is the blank line after it. The comment stays, because the emitter keeps comments unless you ask it not to. : CartItem is gone from the tea line. Both versions print 6. The emitter can also write older JavaScript for older browsers, and .d.ts files that describe your code's types; tsconfig: output covers the options.

The table below sums up the five parts. Read each row as: this part, what it reads, what it makes, and the errors it reports.

The five parts of the compiler
The partWhat it readsWhat it makesErrors it reports
ScannerThe characters of one file.Tokens: keywords, names, numbers, and punctuation.A few, like text that is never closed: Unterminated string literal.
ParserThe tokens.A syntax tree for the file, called the AST.Syntax errors, like ')' expected, mostly numbered in the 1000s.
BinderThe syntax tree.Symbols: one per declared name, kept in a table for each scope.A name declared twice in one scope, like Cannot redeclare block-scoped variable.
CheckerThe trees and symbols of every file.The type of each name and expression.Type errors, like Type 'string' is not assignable to type 'number', mostly numbered 2000 and up.
EmitterThe syntax tree.JavaScript, and .d.ts files when you ask for them.Almost none of its own. It writes the file even when other parts found errors.

One Program, two drivers: tsc and your editor

Real projects have many files, and they import from each other. The compiler gathers them all into one Program: a SourceFile (one file's text and syntax tree) for every file in the project, every library file TypeScript adds (like lib.dom.d.ts, which describes the browser), and the compiler options. The checker works on the whole Program, so it can follow an import into another file:

export function formatPrice(cents: number) {  return `$${(cents / 100).toFixed(2)}`;}
import { formatPrice } from "./price.ts"; const label = formatPrice(450);//    ^?console.log(label);

price.ts exports a formatPrice function that turns cents into dollars. In cart.ts, line 1 imports it, and line 3 calls it with 450. The query on line 4 shows const label: string: the checker followed the import to price.ts's SourceFile and worked out what formatPrice gives back. Line 5 prints $4.50.

A crate labelled Program holds three files: cart.ts, price.ts, and lib.dom.d.ts. On the left, a terminal running tsc checks every file once and writes JavaScript. On the right, an editor keeps the Program and asks it questions as you type.terminaltsccheck every file,write JavaScriptruns oncecart.tsprice.tslib.dom.d.tsProgrameditorhover, errors,completionsasked againas you typekeeps it openthe same compiler answers both
A Program is every file in the project plus the compiler options. tsc builds it, checks every file, writes JavaScript, and stops. Your editor keeps it open and asks it questions as you type.

Two tools drive the compiler, in different ways:

  • tsc, the command, builds the Program once, checks every file, writes the JavaScript, and stops.
  • Your editor talks to TypeScript's language service: the same compiler, wrapped so it can answer questions. In VS Code it runs in a separate process called tsserver. The editor asks "what are the errors in this file?", "what type is at this spot?", "what could come after this dot?". When you change a file, the language service builds a new Program for the next question, reuses the trees of the files you didn't change, and asks the checker only what the question needs.

The table below compares the two. Read each row as a question, then how each one answers it.

tsc and the language service
QuestiontscYour editor's language service
When does it run?When you type tsc in a terminal or a build script runs it.All the time, while the file is open.
How much does it check?Every file in the Program, then it stops.Mostly the files you have open, and only what each question needs.
What does it give you?A list of errors in the terminal, and JavaScript files.Red underlines, hovers, completions, rename, and go to definition.
Does it keep the Program?No. The next run starts again, unless you use --watch.Yes. After each change, it builds a new Program and reuses the trees of the files you didn't change.

The checker on this page is a language service too. When a TypeScript example comes near the screen, the page starts a Web Worker (a script that runs beside the page, so typing never waits for it), downloads the real compiler, version 5.9.2, and the library files it needs, and asks it for errors and // ^? answers. The playground in this lesson asks the same compiler for its tokens, tree, and scopes. Edit & run is different: to run your code it only removes the types, with a faster tool that never checks them (TypeScript tooling explains this split).

TypeScript 7: the same parts, written in Go

The compiler this lesson shows is written in TypeScript and runs as JavaScript. In March 2025 the TypeScript team announced a port to the Go language, codenamed "Corsa". It shipped as TypeScript 7.0 on July 8, 2026, about 8 to 12 times faster on full builds. It is a port, not a new design: the same parts and the same checking rules as TypeScript 6.0, the last version written in TypeScript. (6.0 changed some defaults, like turning strict on, and 7.0 keeps them.) Other tools, like this page's checker, call the compiler from JavaScript through its programming interface. TypeScript 7.0 has none yet; 7.1, in beta since October 20, 2026, adds a new and different one. So this page runs TypeScript 5.9, written in TypeScript. (Checked against the TypeScript team's announcements in October 2026.)

A broken checkout, one part at a time

Here is a checkout script with two mistakes, from two different parts of the compiler. It reads a quantity from a text box (Form elements covers .value) and lets the shopper pay when the quantity is above 0. Press each button and watch the list under the code:

Playground: fix the checkout one part at a time
A checkout with a quantity boxPop out in the code editor (opens in a new tab)TypeScript
const quantityInput = document.createElement("input");quantityInput.value = "2"; function readQuantity(input: HTMLInputElement): number {  return input.value;} if (readQuantity(quantityInput) > 0 {  console.log("Ready to pay");}
When it runscan't run: line 8 isn't valid JavaScript
Who reports what
The parser1 error: line 8
The checker1 error: line 5
Try it yourself

Two errors from two parts of the compiler. Line 8 is missing its ), so the parser reports ')' expected. Line 5 gives back text where the function promised a number, so the checker reports Type 'string' is not assignable to type 'number'. The parser filled in the bracket and carried on, which is why the checker could still find line 5.

TypeScript checks the code for real each time. The buttons change only line 5 and line 8. Reset brings both mistakes back.

With Both mistakes, line 8 gets ')' expected from the parser, and line 5 gets Type 'string' is not assignable to type 'number' from the checker: a text box's .value is always text, and line 4 promised a number. Both errors show at once because the parser recovered. Fix the bracket leaves the checker's error, and the code runs and prints Ready to pay by luck. Fix the type leaves the parser's error, and the code can't run at all. Fix both clears the list.

When you see a long list of errors, look for the 1000s first. A syntax error can confuse the parser's guess about everything after it, and fixing it often makes other errors disappear.

Which part reports it?
  • const total = ;
  • if (true { }
  • const label = "Tea;
  • let size = "M"; let size = "L";
  • class Cart {} class Cart {}
  • const price: number = "3";
  • console.log(totl);
  • const tax = 2; tax = 3;
Try it yourself
0 of 8 correct

Sort each line by the part of the compiler that reports its error. Each line is checked on its own.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Common mix-ups

  • "TypeScript runs my code to find the errors." No. No part of the compiler runs your code. The checker reads the tree and the symbols and reasons about types.
  • "A syntax error stops the checker." No. The parser recovers and builds a whole tree, so the checker still runs. The checkout showed a parser error and a checker error at once.
  • "The emitter needs the types to be right." No. It writes JavaScript from the tree even when the checker reports errors, unless you turn on noEmitOnError.
  • "My editor has its own, simpler type checker." No. It asks TypeScript's language service, which uses the same compiler as tsc. That is why the editor and tsc report the same errors.
  • "The syntax tree is just my text split into lines." No. It is a tree of nodes with kinds, like VariableDeclaration. Spaces and comments aren't nodes; each node only remembers where it starts and ends in the text.
  • "TypeScript 7 checks my code with new rules." No. It is the same compiler ported to Go. It is faster, and it checks code the same way as TypeScript 6.0, whose new defaults it keeps.

Practice exercises

Use the playground in the scanner and parser section to check your answers: type a starter's line there and pick a part. Press Edit & run on code to see its errors and what it prints.

Exercise 1 · Warm-upCount the tokens

How many tokens does the scanner read in this line?

Starter codePop out in the code editor (opens in a new tab)TypeScript
let quantity = 2;

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · Warm-upWhich part reports it?

    This line has an error. Which part of the compiler reports it?

    Starter codePop out in the code editor (opens in a new tab)TypeScript
    const total = 4 *;

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeFix what the parser reports

      The parser marks line 3. Fix it. What does the fixed code print?

      Starter codePop out in the code editor (opens in a new tab)TypeScript
      const prices = [3, 4];let total = 0;for (const price of prices {  total += price;}console.log(total);

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeWhich discount does it use?

        What does this print? Decide which discount line 5 uses before you run it.

        Starter codePop out in the code editor (opens in a new tab)TypeScript
        const discount = 5; function checkout(total: number) {  const discount = 1;  return total - discount;} console.log(checkout(10));

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeRead the tree

          In the parser's tree, what kind of node is the whole of line 2, cart.push("Tea");?

          Starter codePop out in the code editor (opens in a new tab)TypeScript
          const cart: string[] = [];cart.push("Tea");console.log(cart.length);

          Answer, then press Check. Spacing and letter case don’t matter.

            Exercise 6 · ChallengePredict the emitter's output

            How many lines of JavaScript does the emitter write for this file?

            Starter codePop out in the code editor (opens in a new tab)TypeScript
            type Size = "small" | "large";interface Order {  size: Size;}const order: Order = { size: "large" };console.log(order.size);

            Answer, then press Check. Spacing and letter case don’t matter.

              Check your understanding

              For each question, think about which part of the compiler does the work. The quiz shows its code without the checker, so the error list doesn't give the answers away.

              The Inside the compiler quiz · 7 questionsScore: first tries count
              1. Question 1 of 7In what order do the parts of the compiler work on your code?

                Choose an answer to see the explanation.

              2. Question 2 of 7What does the scanner hand to the parser?

                Choose an answer to see the explanation.

              3. Question 3 of 7Which part of the compiler reports the error in this code?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                const ready = true;
                if (ready {
                  console.log("Go");
                }

                Choose an answer to see the explanation.

              4. Question 4 of 7These two lines get the same error twice. Which part reports it?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                let size = "M";
                let size = "L";

                Choose an answer to see the explanation.

              5. Question 5 of 7You point at a name in VS Code and see its type. What answered?

                Choose an answer to see the explanation.

              6. Question 6 of 7What is a Program in the TypeScript compiler?

                Choose an answer to see the explanation.

              7. Question 7 of 7Does tsc still write JavaScript for this file, which has a type error?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                const price: number = "3";
                console.log(price);

                Choose an answer to see the explanation.

              Key takeaways

              • The scanner turns your text into tokens, skipping spaces and comments.
              • The parser builds a syntax tree (the AST) from the tokens, reports syntax errors, and recovers so the rest still runs.
              • The binder makes a symbol for every declared name and puts it in a scope; the closest scope wins.
              • The checker works out types lazily and reports type errors. The emitter writes JavaScript from the tree, even when there are errors.
              • A Program is every file plus the options. tsc checks it once; your editor's language service keeps it and answers questions.
              • TypeScript 7 is the same compiler ported to Go: faster, and it checks code the same way as TypeScript 6.0.

              Remember the one-liner.
              Text becomes tokens, tokens become a tree, names become symbols, the checker finds the types, and the emitter writes the JavaScript.

              Coming next: How inference works. It looks inside the checker at how it works out a type you never wrote.

              CompleteFrontend Clear concepts. Working examples.