The compiler works on a Program: the set of source files (your roots plus everything they import and the lib files) and the compiler options. The compiler notes describe the stages on that program:
- Scanner turns text into tokens (
const,x,:,number, …), onescan()call at a time. - Parser drives the scanner and builds the AST. Each file becomes a
SourceFilenode. Type annotations are still in the tree here. - Binder walks each AST and creates a Symbol for every declaration, stored in a symbol table per scope. It also builds the control-flow graph used for narrowing. Declarations that share a name, like two
interface Boxblocks or a class and a namespace, end up on one merged symbol. - Checker is the type system: it assigns types to symbols, resolves relationships, and reports semantic diagnostics. The notes say it computes lazily and only examines what contributes to the question asked.
- Emitter takes the program and, through transformers, rewrites the AST (types removed, syntax down-levelled for
target, modules converted), then a printer writes.js,.d.tsand.js.map.
Two points interviewers like. First, syntax errors come from parsing and type errors from the checker, and type checking is not required for emit, which is why transpileModule and noCheck work. Second, the language service (tsserver, the editor features) is not a separate compiler: it builds on the same parser, binder and checker to answer hover, completion and rename, and the notes credit incremental parsing for keeping server mode fast.
Version note: TypeScript 7 is a Go port of this structure, with parsing, checking and emitting done in parallel; the stages are the same. Its npm package ships no JavaScript compiler API, so the demo below uses 5.9.3 and 6.0.3.