Ryan Peterman
Go won the TypeScript compiler port over Rust because Rust's borrow checker cannot accommodate the codebase's circular data structures without forcing a complete architectural rewrite.
AI-assisted summary of Creator of TypeScript: Why We Chose Go For Our Rewrite | Anders Hejlsberg — original source. Check the original for full context.
Source published:
Key ideas
- The TypeScript compiler is transitioning away from JavaScript to native code to eliminate a baseline 2x to 3x compute penalty and unlock multi-core scaling. (0:00)
- The team pursued a direct port rather than a clean-slate rewrite to preserve exact compiler semantics, diagnostics, and backwards compatibility for existing codebases. (2:52)
- Go was chosen because its built-in garbage collection, first-class functions, and shared-memory concurrency closely matched TypeScript's architectural assumptions. (2:52)
- Rust was evaluated but deemed unfeasible due to the borrow checker's inability to handle the compiler's heavily cyclic data structures, including AST parent pointers and recursive types. (4:47)
Detailed notes
## Background Published on YouTube by Ryan Peterman on September 11, 2026, this interview clip features TypeScript and C# creator Anders Hejlsberg discussing the technical motivations behind rewriting the TypeScript compiler. According to the description, the conversation explores how moving to Go yielded a 10x performance boost, as well as broader thoughts on AI in software engineering.
## TL;DR Anders Hejlsberg explains that the TypeScript compiler outgrew JavaScript due to pure compute execution penalties and single-threaded memory constraints that prevented it from leveraging multi-core hardware. Rather than undertaking a greenfield rewrite that risked breaking subtle compatibility behaviors, the team executed a direct port that required native code generation, garbage collection, and shared-memory concurrency. Go fit these requirements seamlessly, whereas Rust was rejected after experimentation because its borrow checker clashed fundamentally with the compiler's heavily circular ASTs, recursive types, and symbol graphs.
## Atmosphere & style The conversation carries a relaxed, master-class atmosphere characterized by pragmatic systems-engineering wisdom rather than dogmatic language advocacy. Hejlsberg speaks with calm clarity and self-effacing humor, visibly amused when recalling how quickly Rust's borrow checker stymied their attempt to preserve complex graph structures. Listening feels like sitting in on an honest, high-level post-mortem where decades of language design experience cut cleanly through industry hype.
## Bulletin - [00:00](https://www.youtube.com/watch?v=rX9kkMonrG8&t=0s) The TypeScript compiler is transitioning away from JavaScript to native code to eliminate a baseline 2x to 3x compute penalty and unlock multi-core scaling. - [02:52](https://www.youtube.com/watch?v=rX9kkMonrG8&t=172s) The team pursued a direct port rather than a clean-slate rewrite to preserve exact compiler semantics, diagnostics, and backwards compatibility for existing codebases. - [02:52](https://www.youtube.com/watch?v=rX9kkMonrG8&t=172s) Go was chosen because its built-in garbage collection, first-class functions, and shared-memory concurrency closely matched TypeScript's architectural assumptions. - [04:47](https://www.youtube.com/watch?v=rX9kkMonrG8&t=287s) Rust was evaluated but deemed unfeasible due to the borrow checker's inability to handle the compiler's heavily cyclic data structures, including AST parent pointers and recursive types.
## Key takeaways - [00:00](https://www.youtube.com/watch?v=rX9kkMonrG8&t=0s) The decision to rewrite the TypeScript compiler in native code was driven entirely by performance and scalability needs. - [00:00](https://www.youtube.com/watch?v=rX9kkMonrG8&t=0s) JavaScript was designed for browser UI tasks and small scripts, incurring an immediate 2x to 3x performance penalty on pure compute workloads like compilers. - [00:00](https://www.youtube.com/watch?v=rX9kkMonrG8&t=0s) JavaScript's single-threaded nature shields developers from concurrency bugs but leaves modern multi-core hardware severely underutilized. - [00:00](https://www.youtube.com/watch?v=rX9kkMonrG8&t=0s) Web workers do not support shared-memory concurrency, forcing expensive data serialization rather than direct access to in-memory object graphs. - [00:00](https://www.youtube.com/watch?v=rX9kkMonrG8&t=0s) Because Moore's Law now scales through additional CPU cores rather than clock speeds, shared-memory concurrency is essential to avoid leaving performance on the table. - [02:52](https://www.youtube.com/watch?v=rX9kkMonrG8&t=172s) The team chose a line-by-line port instead of a greenfield rewrite to guarantee bug-for-bug compatibility and preserve subtle type-checking behaviors. - [02:52](https://www.youtube.com/watch?v=rX9kkMonrG8&t=172s) Porting required a target language that naturally supported garbage collection and closures capturing outer state. - [02:52](https://www.youtube.com/watch?v=rX9kkMonrG8&t=172s) Go offered mature native code generation, built-in garbage collection, and shared-memory primitives that aligned directly with the existing TypeScript codebase. - [04:47](https://www.youtube.com/watch?v=rX9kkMonrG8&t=287s) Rust's ownership model and borrow checker fundamentally reject circular references and shared mutable graphs. - [04:47](https://www.youtube.com/watch?v=rX9kkMonrG8&t=287s) The TypeScript compiler relies extensively on cyclic data structures, such as syntax trees with upward parent pointers, recursive types, and interconnected symbol tables. - [04:47](https://www.youtube.com/watch?v=rX9kkMonrG8&t=287s) An experimental port to Rust was abandoned because it required redesigning core data structures and created self-inflicted architectural friction. - [04:47](https://www.youtube.com/watch?v=rX9kkMonrG8&t=287s) Workarounds for cyclic graphs in Rust, such as reference counting and arenas, frequently lead to using unsafe blocks that undermine safety guarantees. - [04:47](https://www.youtube.com/watch?v=rX9kkMonrG8&t=287s) Go delivered the necessary performance gains and concurrency benefits with substantially lower implementation friction than Rust.
## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=rX9kkMonrG8&t=0s) The Bottlenecks of JavaScript: Compute Penalties and Single-Threaded Limits
Ryan Peterman opens the discussion by asking Anders Hejlsberg about the decision to move the TypeScript compiler away from its original pure JavaScript implementation and rewrite it in native code. Anders explains that the motivation boils down to "performance and scalability." JavaScript was originally designed to handle browser user interfaces and run small scripts—perhaps roughly a hundred lines of code—rather than execute heavy compute workloads like language compilers. When running pure compute tasks in JavaScript, developers pay an immediate 2x to 3x performance penalty compared to native machine code.
Even more restrictive than raw execution speed is JavaScript's lack of concurrency. Anders highlights that JavaScript was fundamentally designed as a single-threaded environment, which is why the ecosystem relies heavily on callbacks, promises, and `async`/`await`. While single-threading shields developers from the subtle bugs, race conditions, and deadlocks common in languages with mutable state—unlike functional programming languages where immutable data simplifies reasoning—it leaves modern hardware severely underutilized. JavaScript offers web workers, but they do not support shared-memory concurrency; data passed between workers must be remoted or serialized (such as via JSON) rather than directly referencing shared in-memory object graphs.
With Moore's Law no longer delivering faster single-core clock speeds, hardware manufacturers instead supply more CPU cores. Because TypeScript could not take advantage of those cores through shared-memory multithreading, Anders notes the team was "leaving money on the table in multiple ways." To capture all the available hardware performance, they needed a language that produced fast native code and provided true shared-memory concurrency.
```mermaid flowchart TD accTitle: TypeScript Execution Constraints accDescr: Comparison of JavaScript execution limits versus native shared-memory architecture.
subgraph JS["JavaScript Compiler Constraints"] JS_Perf["2x–3x Compute Penalty vs Native"] JS_Thread["Single-Threaded Model"] JS_Workers["Web Workers (Isolated Memory / JSON Serialization)"] JS_Perf --- JS_Thread --- JS_Workers end
subgraph Native["Native Multi-Core Goal"] Native_Exec["Direct Native Execution"] Shared_Mem["Shared-Memory Concurrency across Cores"] Native_Exec --- Shared_Mem end
JS -.->|"Port to Native Runtime"| Native ```
[02:52](https://www.youtube.com/watch?v=rX9kkMonrG8&t=172s) Porting vs. Rewriting: Why Go Checked Every Box
When asked why Go was selected over popular modern systems languages like Rust or Zig, Anders clarifies an essential distinction: the team decided early on not to do a greenfield rewrite, but rather a direct port. Porting was mandatory to preserve the exact semantics, internal algorithms, and idiosyncratic behaviors of the existing TypeScript compiler, which millions of developers rely upon for backwards compatibility. A total rewrite from scratch would inevitably yield subtle discrepancies in type-checking decisions, different error diagnostics, or diverging behavior when resolving ambiguous language constructs.
Because the strategy was a line-by-line port, the target language had to match the fundamental architectural assumptions already baked into the TypeScript codebase. Specifically, the TypeScript compiler heavily depends on garbage collection and first-class functions (closures that capture outer state). Go emerged as the clear winner because it matched these design requirements without friction:
- **Mature Native Code Generation**: Highly robust, battle-tested compilation targets across all major operating systems and architectures. - **Built-in Garbage Collection**: Native memory reclamation that seamlessly accommodates existing JavaScript object lifetimes. - **Shared-Memory Concurrency**: Clean, powerful primitives for running concurrent operations across multiple CPU cores against shared data structures.
Anders notes that while other languages had individual strengths, virtually every alternative had characteristics that actively fought against their primary porting goals. For this specific workload, Go was uniquely aligned.
[04:47](https://www.youtube.com/watch?v=rX9kkMonrG8&t=287s) Why Rust Fell Short: The Problem with Circular Graphs and Borrow Checking
Ryan specifically asks what Rust lacked that prevented them from adopting it. Anders points directly to Rust's ownership model and borrow checker. While Rust avoids runtime garbage collection by enforcing strict compile-time ownership, its borrow checker fundamentally rejects circular references and shared mutable graphs. Anders emphasizes that the TypeScript compiler is "chock full of circular data structures"—including syntax trees with upward parent pointers, recursive type definitions, and complex symbol tables referencing one another in cycles.
The team actually attempted to make Rust work, but Anders admits with a laugh that "it just wasn't feasible." Successfully adapting the compiler to Rust would have required completely redesigning the data structures and solving a multitude of self-inflicted architectural problems instead of directly porting the logic. Furthermore, Anders points out that Rust would not have delivered meaningful advantages over Go in real-world code quality or multithreaded concurrency gains for this workload. Choosing Go allowed the team to get all the performance benefits ("all the bennies") with significantly less developer friction.
- Rust's borrow checker rejects cyclical graphs, whereas compiler ASTs require parent links, recursive types, and cross-referencing symbols. - Alternative memory management in Rust (such as reference counting or arena allocators) frequently requires reaching for `unsafe` blocks, compromising the desired safety guarantees. - Go provides out-of-the-box type safety and memory safety without stray pointers, treating garbage collection as a separate, fully integrated runtime concern rather than a manual burden imposed on every data structure.
## Notable quotes > [00:00](https://www.youtube.com/watch?v=rX9kkMonrG8&t=0s) performance and scalability — Anders Hejlsberg
> [00:00](https://www.youtube.com/watch?v=rX9kkMonrG8&t=0s) When running pure compute tasks in JavaScript, developers pay an immediate 2x to 3x performance penalty compared to native machine code. — Anders Hejlsberg
> [00:00](https://www.youtube.com/watch?v=rX9kkMonrG8&t=0s) JavaScript was fundamentally designed as a single-threaded environment, which is why the ecosystem relies heavily on callbacks, promises, and async/await. — Anders Hejlsberg
> [00:00](https://www.youtube.com/watch?v=rX9kkMonrG8&t=0s) leaving money on the table in multiple ways — Anders Hejlsberg
> [02:52](https://www.youtube.com/watch?v=rX9kkMonrG8&t=172s) the team decided early on not to do a greenfield rewrite, but rather a direct port. — Anders Hejlsberg
> [02:52](https://www.youtube.com/watch?v=rX9kkMonrG8&t=172s) Porting was mandatory to preserve the exact semantics, internal algorithms, and idiosyncratic behaviors of the existing TypeScript compiler — Anders Hejlsberg
> [02:52](https://www.youtube.com/watch?v=rX9kkMonrG8&t=172s) virtually every alternative had characteristics that actively fought against their primary porting goals. — Anders Hejlsberg
> [04:47](https://www.youtube.com/watch?v=rX9kkMonrG8&t=287s) chock full of circular data structures — Anders Hejlsberg
> [04:47](https://www.youtube.com/watch?v=rX9kkMonrG8&t=287s) it just wasn't feasible — Anders Hejlsberg
> [04:47](https://www.youtube.com/watch?v=rX9kkMonrG8&t=287s) Rust would not have delivered meaningful advantages over Go in real-world code quality or multithreaded concurrency gains for this workload. — Anders Hejlsberg
> [04:47](https://www.youtube.com/watch?v=rX9kkMonrG8&t=287s) all the bennies — Anders Hejlsberg
## Mentioned Anders Hejlsberg, Ryan Peterman, TypeScript, Go, JavaScript, Rust, Zig, C#, Moore's Law, JSON
## Video description Anders Hejlsberg is the creator of TypeScript and C#, and I asked him about how the TypeScript compiler got 10x faster through a rewrite in Go and his thoughts on how AI has impacted software engineering.
Watch the full episode: https://www.youtube.com/watch?v=cywK3XYYJ2o
[Source](https://www.youtube.com/watch?v=rX9kkMonrG8)