Microsoft Rewrites TypeScript in Go, Achieving 10x Performance Boost

Months ago, TypeScript announced a complete rewrite in the Go language. The rewritten TypeScript achieved a speed increase of up to 10 times in certain tests (for example, in the VS Code project), with some cases even reaching 15 times.A 10x Faster TypeScript[1]

Microsoft Rewrites TypeScript in Go, Achieving 10x Performance Boost

In just a few days, the official library typescript-go[2] surpassed 14,000 stars, and various articles began to emerge. However, there is a common question: why did Microsoft choose Go instead of Rust, which has been rewriting everything in recent years? (Why Go?[3])

To address this,<span><span>Michigan TypeScript</span></span> channel host and Rust developer interviewed TypeScript co-founder and chief architect Anders Hejlsberg (translated as 安德斯·海尔斯伯格, a Danish legend in programming languages, the father of Delphi, C#, and TypeScript). Below is a summary and translation of the full content[4] with appropriate edits for clarity.

Host’s Introduction:

JavaScript was never designed for compute-intensive system-level workloads, right? But Go was born for that purpose. Our pursuit this time is complete compatibility; what we truly want is to make it a plug-and-play replacement for the old compiler. However, I think a more interesting question is: if there is a type checker that is 10 times faster than before, what impact will that have?

When we think about this question in conjunction with advancements in AI and Agentic Programming, what can we do with the information generated at 10 times the speed? For instance, can we provide more contextual information for LLMs (large language models)? What are the actual parsing results for these types? What does a certain symbol represent? Where is it declared? Currently, LLMs can only see the spelling of symbols and cannot truly understand their meanings.

If we could provide LLMs with higher precision information in real-time, what changes would that bring?

The results show that TypeScript’s type checker <span><span>---</span></span> as one of the most fundamental software development tools <span><span>---</span></span> has now increased its speed by 10 times. This is because the team has migrated the codebase to Go, a process that has been ongoing for several months. When TypeScript’s chief architect and co-founder Anders Hejlsberg invited me to discuss this significant change, I tried to think of some questions <span><span>---</span></span> that might concern library authors, experienced users, tool developers, and compiler contributors.

My primary job is writing Rust, and I know many people are curious: why didn’t they choose Rust? I had the same question at first. But in my conversation with Anders, he shared the team’s vision for the future path. After hearing the technical considerations, I completely agree that Go is the right choice.

However, I hope that what everyone focuses on is not which native language TypeScript has migrated to, but that the speed of type checking has increased by 10 times, while memory usage has been halved, and concurrent processing has been achieved. This means that TypeScript will enter a new era, bringing many new possibilities. I hope you can gain deeper insights into this transformation through Anders’s sharing.

Interview Content:

Host: Hello everyone, today we have Anders Hejlsberg with us. He will introduce a significant event in the TypeScript field and answer some questions. We hope to discuss the impact of this change from the perspective of library authors and developers who deeply use TypeScript.

Anders, how are you! Please introduce yourself to everyone!

Anders: I’m good, thank you! I am Anders Hejlsberg, a Technical Fellow at Microsoft, currently serving as the chief architect of the TypeScript open-source project. Before this, I worked on the C# language project for over ten years. Even earlier, I worked at Borland, responsible for the development of Delphi and Turbo Pascal. So, I have been involved in programming languages and software development tools for over 40 years, even longer.

Host: Wow, what an amazing career! Actually, not long ago, I particularly enjoyed writing C language and appreciated all aspects of it, so I want to thank you! But then TypeScript came along…

Perhaps we can start with the initial story. The new project is codenamed Corsa, right? And the old codebase was codenamed Strata. I heard that Strata was actually the earliest codename for TypeScript, is that correct?

Anders: Yes, that’s right! Around late 2010 to early 2011, we started working on this project. At that time, we were developing TypeScript internally in the company, and the initial codename was Strata.

Host: The main members of this project at that time were you and Luke, right?

Anders: Yes, of course, there was also Steve Lucco. He wrote the initial prototype compiler. At that time, he built the TypeScript compiler prototype by leveraging the lexer (Scanner) and parser from the JavaScript engine of Internet Explorer, reusing these components. That was a codebase written in C, just to create a proof-of-concept. Luke was our product manager at that time. So, initially, Steve, I, and Luke formed the core team of TypeScript.

Host: Awesome! Fast forward to recent times… Can you tell us who proposed the Corsa project? When did this happen?

Anders: Well… there wasn’t a specific day. This idea has been in our minds for a long time. You should have noticed that many key tools in the ECMAScript ecosystem have started migrating to native code, such as esbuild[5], SWC[6] (the former is a web bundler developed in Go, and the latter is a TypeScript/JavaScript compiler written in Rust), etc. There are now multiple JavaScript parsers and linters written in native code on the market.

We have been monitoring these trends, and in fact, we have used esbuild in the construction of the TypeScript compiler. Additionally, we observed that several teams in the community attempted to reimplement TypeScript in native code. Some teams built from scratch, while others attempted migration, but unfortunately, none of these projects made a significant impact.

This is understandable because TypeScript is a very complex project <span><span>---</span></span> currently, we have invested about 100 person-years of development work in TypeScript. Therefore, for an individual developer, migrating or rewriting a highly compatible version of TypeScript is almost an impossible task. It is a massive undertaking.

We have been observing these attempts in the community while also engaging in extensive discussions about performance and scalability, as these are among the most common requests from TypeScript users <span><span>---</span></span>“Can we make it scale better? Can we make it run faster?”

Microsoft Rewrites TypeScript in Go, Achieving 10x Performance Boost

Yes, it does need to be faster. As software evolves, the codebase will only grow larger, not smaller. Project sizes are continuously increasing, and our compiler is also getting larger. This puts more pressure on runtime environments, such as the V8 engine and JavaScript engines. Since JavaScript uses Just-In-Time (JIT) compilation, as we keep adding new features, the startup time of TypeScript is gradually increasing.

We have been observing the slow growth trend of TypeScript’s runtime, or rather, its gradual slowdown. Therefore, we made some performance optimization attempts and conducted a series of improvements. However, these optimizations typically only yield a 5% or 10% improvement, without any substantial breakthroughs. We gradually realized that we were nearing the limits of our optimization potential.

When we used performance profiling tools to examine the TypeScript compiler’s operation, we found that it had no significant performance bottlenecks (Hotspots). It was already running as fast as possible, and all optimization avenues had been exhausted.

Therefore, in August of last year, we began to consider: What impact would migrating TypeScript to native code have? We needed to gather some data to make a more informed decision about whether this migration was worthwhile.

So, we started prototyping in different languages (Prototyping). We tried Rust, Go, C, and several other languages. Ultimately, we found that Go met our needs very well.

In August, I began migrating TypeScript’s scanner and parser to Go to establish a baseline and see how fast it could perform and how difficult it would be to migrate from JavaScript to Go.

The results were much smoother than we expected. In just a few months, we achieved a working version that could parse all our source code without errors.

From this stage, we could infer some performance data. We gradually realized that this migration could lead to a 10x performance improvement!

Of this, about 3 to 3.5 times of the improvement comes from the execution efficiency of native code, while another 3 to 3.5 times comes from concurrent execution (Concurrency). Combining both, we can achieve a 10x performance boost.

A 10x speed increase is a significant breakthrough! Once you see such potential, it is hard to abandon this direction. Compared to that, other optimization methods seem trivial.

Host: What was the reaction of other team members when they learned this news? Was there a day when everyone suddenly realized, “Wow, this solution is really feasible!”

Anders: I think the internal reaction of the team was a mix of excitement and tension.

On one hand, everyone was excited about this technical direction because it brought unprecedented performance improvements. On the other hand, it was also an unknown territory; could we really succeed? Would team members be able to quickly master Go? Would Go’s toolchain be as user-friendly as TypeScript?

After all, we had been working in the TypeScript codebase for over ten years, and suddenly migrating to an entirely new language brought many unknowns. This made us both excited and a bit anxious.

Host: You just mentioned the concept of a “Self-Hosted Language.” For listeners who are not familiar with this concept, could you briefly explain it?

Anders: Of course. Many programming languages were initially written in other languages, such as Go, which was originally written in C, and Rust, which was initially written in OCaml. But once these languages reach a certain level of maturity, they can implement their own compilers using themselves.

TypeScript is also like this <span><span>---</span></span> it is written in TypeScript itself, which means we have been developing TypeScript using TypeScript.

However, this migration is equivalent to “ejecting from self-hosting,” which is not common in the history of programming languages.

Host: So, this is equivalent to TypeScript “decoupling” from the JavaScript ecosystem and using Go as the core implementation?

Anders: Yes, many tools in the JavaScript ecosystem have migrated from JavaScript to native code, such as esbuild, SWC, etc. They were initially written in JavaScript but later migrated to native languages for performance improvements.

We are also seriously considering this issue. In fact, one of our biggest concerns is whether ejecting from self-hosting will have negative consequences. After all, TypeScript has always been written in TypeScript, and this self-hosting model has brought us many benefits.

Currently, we are still exploring the final architectural solution. It is certain that the core part of the TypeScript language service, especially the semantic analysis engine, will be in native code. However, many parts of the TypeScript ecosystem may continue to be written in JavaScript.

The compiler provides all the information, but there are still many things around it that may continue to remain in JavaScript. We know we need to build an API between the native part, Go, and consumers who wish to use other languages.

This is a problem we have not fully resolved yet. But we do see the value of self-hosting. However, we must also be realistic. In the long run, if we do not consider other options, we may lose the 10x performance improvement. So we need to weigh which option ultimately benefits the community more.

Host: I completely agree! I hope everyone can take away this message from our discussion. I should mention that my work mainly involves using Rust, and I was very excited when I heard this news. An obvious question for many is: Why not Rust? Because many tools in the community are gradually leaning towards Rust. So, I want to ask you this question directly. I know you have considered Rust, and I would also like to hear why you ultimately did not choose Rust. Additionally, I would like to know if C# was ever considered. My understanding is that C# has made significant progress in asynchronous processing, thread pools, etc., in recent years. Perhaps you could discuss these two options?

Anders: One key factor is that we are migrating existing code rather than starting from scratch. If we were starting from scratch, the choice of language could be determined by project requirements. For example, if we were writing Rust from the ground up, we would design a compiler that does not rely on automatic garbage collection (GC) and does not overly depend on circular references.

But the reality is that our product has over a decade of history, millions of programmers using it, and millions of lines of code running. Therefore, we inevitably encounter various compatibility issues. Many behaviors in our compiler are “arbitrarily” decided <span><span>---</span></span> for instance, in type inference, there may be multiple candidates that are all correct, and our compiler will choose one. This behavior has actually become a feature that many programs depend on. If the new codebase handles this differently, it could lead to new errors.

So, from the beginning, we knew the only feasible solution was to migrate the existing codebase. And the existing codebase has some fundamental assumptions, one of which is relying on automatic garbage collection. This premise essentially rules out Rust because Rust does not have automatic GC.

In Rust, you can use manual memory management, reference counting, etc., but Rust has an additional constraint: the borrow checker, which is very strict about ownership management of data structures, especially prohibiting circular data structures. However, our existing codebase is rife with circular data structures, such as:

  • AST (Abstract Syntax Tree) has child nodes pointing to parent nodes and vice versa.
  • Symbol tables may reference declarations, which may in turn reference symbols.
  • Type systems are also highly recursive, with many circular references.

If we were to adapt to Rust, we would have to redesign all these data structures, making the migration to native code nearly insurmountable. Therefore, we needed a language that could generate efficient native code, support circular data structures, and must also have automatic garbage collection.

Additionally, we also need concurrent support, and it must be shared memory concurrency. While JavaScript provides concurrency through Web Workers, it does not support shared memory concurrency. Our compiler requires shared memory concurrency.

When we listed all these requirements, along with our desire for excellent development tools (such as support from VS Code), we ultimately found that Go performed excellently in all aspects. Therefore, we began prototyping in Go and found the experience to be very good, so we continued to push forward.

Host: This is indeed a very realistic consideration. Personally, I am passionate about Rust, but I also understand that Rust is not a language that can be learned over a weekend. Rust focuses on being as correct as possible, even if it affects the developer experience (DX).

Anders: For JavaScript developers, the learning curve for Go is clearly much lower than for Rust; I firmly believe that.

Host: I’m glad to hear you say that because from a human resources perspective, this is also a very important decision-making basis. If you compare JavaScript and Go, their code structures are actually quite similar. But if you compare JavaScript and Rust, especially when it comes to recursive structures, Rust’s code can be very difficult to see as having migrated from JavaScript.

This is also a huge advantage of Go.

Now, let’s add one last question: What about C#?<span><span>C#</span></span> was it ever considered?

Anders: Yes, we did consider <span><span>C#</span></span>, but ultimately we found that Go had greater advantages.

Go is the lowest-level language we could choose while still having automatic garbage collection. It is the closest to native language while still providing GC. In contrast, <span><span>C#</span></span> is more of a “bytecode-first” language; although there are AOT (Ahead-of-Time) compilation options on some platforms, it is not applicable to all platforms. To some extent, C has not been rigorously refined over the past decade; its original design purpose was not for applications like ours. Go is more expressive in terms of data structure layout and inline structs, which is a significant advantage for us.

Moreover, our JavaScript codebase adopts a highly functional programming style, and we hardly use classes; in fact, the core part of the compiler does not use classes at all. Go also has similar characteristics; it mainly consists of functions and data structures, rather than being highly object-oriented like <span><span>C#</span></span>. If we chose <span><span>C#</span></span>, we would have to switch to an object-oriented paradigm, which would increase the resistance to migration, while Go is the least resistant choice.

Host: Great! I have some questions about this. I have encountered many difficulties with functional programming in Go, but it seems that you did not encounter similar issues, which is something I want to ask about.

Anders: When I say “functional programming,” I mean a purely functional style, where we mainly use functions and data structures, rather than objects. I am not referring to concepts like pattern matching, higher-kinded types, or monads. What we are discussing is still a relatively low-level implementation.

Host: I have delved into debugging the TypeScript codebase; although I have not contributed a lot of code, I found that the TypeScript codebase makes extensive use of enums and bitwise operations, especially unary bitwise operations to track state values. However, Go seems to lack a completely similar concept; while it supports constant grouping, which looks a bit like enums, I am curious how you handle this in Go?

Anders: What you mentioned essentially indicates that TypeScript has a richer type system than Go, and I completely agree with that. Go does not have a true concept of enums, although it supports constant grouping and the Iota mechanism (automatic numbering), but it still feels a bit odd, and the type checking support is not as complex as TypeScript.

However, Go’s support for bit manipulation and flag storage far exceeds that of JavaScript. In JavaScript, all data types are essentially floating point numbers, while in Go, you can use various integer types, such as <span><span>int8</span></span>, <span><span>int16</span></span>, <span><span>int32</span></span>, <span><span>int64</span></span>, which can be both signed and unsigned. In contrast, JavaScript even uses 8-byte floating point numbers to store boolean values (true/false), which is clearly inefficient.

In our JavaScript compiler, we adopted some optimization techniques, such as packing 31 bits of information into floating point numbers, but this is still a stopgap measure. In Go, we can use all bits, and even arrange them into inline structs and store them in arrays. This optimization has reduced our memory consumption by about half.

In modern computer architecture, memory consumption directly affects speed. Using more memory leads to frequent access to main memory, which reduces performance. The speed at which the CPU processes instructions is nearly zero cycles when predicting hits, but if a cache miss occurs, it may take thousands of cycles to retrieve data from main memory. Therefore, optimizing data structure layout and reducing memory usage can significantly enhance performance.

Host: Hearing you say this, I start to think about another question. Although Go’s type system is not as complex as TypeScript’s, it does have some unique features. For example, TypeScript allows creating opaque types; does Go have a similar mechanism? Since you use Go every day, are there any features of Go that you would like to introduce into TypeScript?

Anders: Well, your question is interesting and makes me think. However, I can point out that Go’s new version has introduced the concept of fresh types, which allows you to create a variant of <span><span>int32</span></span> that is different from other <span><span>int32</span></span>, which is how we implement type-safe enums in Go.

As for whether there are Go features worth introducing into TypeScript, I find it hard to say. Because Go’s type system is relatively simple, it focuses more on runtime features, while TypeScript is primarily a static type system. However, in terms of runtime features, I do wish JavaScript could have structs like Go, but whether this is suitable for the entire JavaScript ecosystem, I am not quite sure.

After all, the compiler itself is an extremely special JavaScript application scenario. If someone had told me ten years ago that I would be writing a compiler in JavaScript for ten years, I would have thought they were crazy. But the reality is that our team has indeed been developing a compiler using JavaScript all along.

However, JavaScript was not designed for compute-intensive system-level workloads, while Go was precisely designed for such scenarios. Look at large projects based on Go, like Kubernetes, and you will understand this. Go has no UI-related abstractions; it is essentially a system-level tool, and the TypeScript compiler is also a system-level program, so Go is very suitable for our needs.

Host: This makes a lot of sense. Now, let’s delve into how this migration ensures a smooth transition for the entire TypeScript ecosystem. My first question is, TypeScript does not have a formal specification; its reference implementation is the specification itself. So, how do you ensure behavioral consistency when migrating to the new Go codebase?

Anders: This is one of the core reasons we chose to “migrate” rather than “rewrite.” When you migrate code, the final semantics remain unchanged. Although the implementation of the code is different, it will still produce the same behavior when given the same input data.

Host: So, you mean that the migration will not lead to any behavioral changes?

Anders: Yes, our goal is to faithfully maintain the original behavior as much as possible. We have retained all the same types, and the layout of data structures is consistent with the JavaScript version. Of course, in JavaScript/TypeScript, we make extensive use of union types, intersection types, and some advanced type system features that Go does not have, so our type declaration methods will differ somewhat, but the core logic remains consistent. Semantically, we are still discussing the same concepts. This applies to symbols, object models, and the type system within the compiler.

Host: Great! Because library authors may worry about whether they need to maintain two sets of type definitions. It sounds like you are working hard to ensure this transition is smooth.

Anders: Our goal is 99.99% compatibility. Ideally, we want to generate completely consistent error messages for the same codebase. This is precisely the direction we have been striving for.

Currently, our open-source compiler can compile and check the entire Visual Studio Code codebase without errors, and that is a massive codebase containing about 1.5 million lines of code, nearly 5000 source files, totaling about 50MB. Additionally, we are very close to enabling all tests.

We know we can run 20,000 conformance tests without crashing. We are still analyzing benchmark data and eliminating some subtle differences. But our goal is complete compatibility, ensuring it can serve as a seamless replacement for the old compiler. Because only then can we finally relieve the long-term burden of maintaining the old compiler.

Host: Are there any particularly challenging challenges that might affect this transition?

Anders: That’s a good question. If there were no challenges, that would be the best answer (laughs). But in reality, there are indeed some complex situations. For example, we made some adjustments to the representation of types internally, especially regarding type ordering.

When you have a union of types, the order can be important in certain cases, such as when you print out the union type, the order of types determines how the output is displayed. Additionally, in some error messages, we need to choose the appropriate candidates for error reporting, or when performing subtype simplifications, this ordering can have an impact.

In the old compiler, the way types were ordered was relatively simple but non-deterministic. It was deterministic in a single-threaded environment, but in a multi-threaded environment, it could differ. Previously, we would simply assign an incrementing sequence number when creating type objects, which was predictable in a single-threaded environment. However, in a multi-threaded environment, due to the nature of concurrency, this method is no longer applicable. Therefore, we need to introduce a deterministic type ordering method. But this also leads to differences in type ordering in certain cases compared to the old compiler. Although theoretically, the order of union types should not affect functionality, in some cases, it does cause changes, and we need to address these issues.

However, all these issues are solvable. I think the biggest challenge right now may be how to provide a versionable and modern API for the new codebase.

In the old codebase, the source code itself was the API specification. JavaScript allows you to call anything from anywhere, so all internal components of the compiler were exposed as APIs. But in the new architecture, we can no longer do that. In fact, the new codebase currently does not expose any APIs by default, so we need to carefully design a new API and ensure it remains efficient in inter-process communication (IPC) environments, rather than simply making function calls through the call stack.

Host: I completely understand what you mean. I have been involved in some similar projects, and calling a function may work, but in the type system, it may have already been removed (laughs). People do take advantage of these loopholes.

Anders: Yes (laughs). We are indeed seriously considering the design of the JavaScript API and hope it can integrate better with other languages.

Host: I am glad that this project was not written in Rust because it means it can integrate better with other languages. If I wanted to write a code generator (Emitter) in Zig, it might be easier. Have you considered WebAssembly (WASM)? Will you provide bindings for Rust or other languages?

Anders: Our goal is to provide language-neutral bindings. We will definitely support the Language Server Protocol (LSP), as this is the core architecture of our new native language service. This is also the transformation we have always wanted to achieve.

The TypeScript project predates LSP; in fact, TypeScript was one of the inspirations for LSP’s design. However, we have not fully migrated to LSP ourselves, and this refactoring gives us an opportunity. LSP will become a universal API that all tools can leverage. Additionally, we may provide extra functionality on top of LSP so that developers can query more information from the language server.

However, the functionality of LSP is far less rich than the current JavaScript API, so we are also exploring how to provide a richer and more synchronous API, especially if we still want some language services to continue using JavaScript. But at present, we have not finalized the design of the API, and we are actively exploring this area.

Host: How long do you plan to maintain the Strata codebase after transitioning to the new Go compiler?

Anders: We will probably maintain it for a few more years. We are very cautious and do not want to leave any users behind. We understand that some projects may not be able to migrate to the new native compiler immediately, so we will continue to maintain the old TypeScript compiler.

However, by the end of this year, we expect to have a fully functional compiler that the vast majority of users can use. In fact, the command-line compiler part is already very close to completion. Meanwhile, we are developing features such as JSX, JSDoc support, project references, build modes, and watch modes, all of which are in progress.

Currently, we can compile a single project with the new compiler, which is 10 times faster than the old compiler and can provide the same error messages.

Host: That means the TypeScript-Go codebase will become the new main codebase, right? (Translator’s note: As of the time of writing, the star counts for the two are 103k / 14k)

Anders: Yes, in the long run, it will become the primary codebase for TypeScript. Of course, we are still exploring the architecture of the language service, and there may ultimately be a combination of native components and JavaScript components. But overall, the new Go compiler will be the future of TypeScript.

Host: This timeline looks quite aggressive (laughs). But your support cycle is also very long. My friend Andrist has also been on this channel before, and he currently has about 100 PRs (Pull Requests) queued up. I am worried about what will happen to these PRs?

Anders: We will try to migrate these PRs. We selected a baseline commit around August or September last year as the starting point for migration. Therefore, we can start from that point and backtrack to select suitable PRs for migration.

Host: This is good news for tool developers in the TypeScript ecosystem. Their tools, such as linters and formatters, will become faster as a result?

Anders: It is difficult to generalize. It depends on how the tools are implemented and how much they rely on language services. Some tools may only use the parser rather than the complete semantic analysis. However, we are indeed communicating with the main tool developers in the ecosystem to help them migrate or optimize their tools.

Host: That’s great! Your active communication with tool developers is good news for the entire community.

Additionally, I want to ask about the concurrency model. What challenges did you encounter when introducing concurrency? Did you encounter deadlocks or other concurrency issues?

Anders: This is interesting because the TypeScript compiler we are migrating, which everyone has been using for the past decade, is itself a highly functional codebase.

It employs many functional programming patterns, especially in terms of immutability. This strategy ensures safe sharing of data. For example, after we scan, parse, and bind the abstract syntax tree (AST), we essentially treat it as immutable. This means multiple type checkers can access the same AST simultaneously without interfering with each other.

You might ask, “But JavaScript itself does not support concurrency; what impact does this have?” In fact, this is still very important. Because during development, you may open multiple projects, and these projects may all contain the same files. Our approach avoids creating multiple duplicate ASTs, saving a lot of resources. Additionally, it helps us reuse data more efficiently.

When you edit code in the language service, you are actually constantly creating new program views. Because every time you edit a file, the state of the entire codebase changes. However, most of the code does not change, so when rebuilding a new program view, we want to reuse the old view data as much as possible. This is also why immutable data structures are very important.

For example, in a project containing 100 files, if you only modify one file, every time you press a key, the compiler actually needs to rebuild the entire program view. But if we use immutable data structures, then the 99 unchanged files’ ASTs can still be reused directly, and only the currently edited file needs to be updated.

From the beginning, our compiler was designed in this way. It can be said that it is essentially a compiler very suitable for concurrent processing, but it has always been limited in an environment that cannot fully utilize concurrency. And this is precisely the key to the shared memory concurrency I mentioned earlier.

Microsoft Rewrites TypeScript in Go, Achieving 10x Performance Boost

Host: Can you elaborate on how concurrency has helped you? Isn’t JavaScript only able to achieve concurrency through Web Workers? How do these limitations affect your development?

Anders: Yes, JavaScript’s concurrency model mainly relies on Web Workers, but Web Workers are isolated from each other and cannot directly share memory. The only thing they can share is JSON data or byte arrays, but they cannot share structured data.

However, in certain stages of the compiler, such as parsing, we can fully utilize concurrency capabilities. Parsing is a highly parallelizable task. Its basic process is:

  1. Read source files into memory.
  2. Build a data structure for fast parsing and navigating the code.

In this process, parsing each source file is completely independent. If you have 5000 source files and 8 CPU cores, you can divide these files into 8 parts and let each CPU handle parsing one part. In the end, you will get 8 sets of parsed data structures.

However, this method only works if all processes share the same memory space. If we try to do this in JavaScript, we would end up with 8 isolated parsing results, and then we would need to perform inter-process communication to merge them. However, the overhead of inter-process communication is often greater than the parsing itself, making it counterproductive.

But in the new architecture, we can utilize shared memory, allowing all parsing processes to run in the same memory space. This makes parsing 3 to 4 times faster. Moreover, the code changes required to implement parallel parsing are very minimal, requiring only about 10 lines of code, just needing to use Go’s goroutines and mutexes to protect shared resources, such as a unique ID generator. Ultimately, our parsing speed has significantly improved.

Host: It only took 10 lines of code to achieve such a significant improvement? That’s amazing!

Anders: Yes (laughs). However, the concurrency optimization for type checking is not that simple.

Unlike parsing, type checking is not a file-level independent task. The core idea of the type checker is a whole program view, which means:

  • Code can import types from other files,
  • The type of a variable may depend on the context of the entire project,
  • This means that the type checking process will constantly cross file boundaries.

For example, if you write <span><span>let x: SomeType</span></span>, then <span><span>SomeType</span></span> may be defined in another file. The type checker must jump to that file, parse the information of <span><span>SomeType</span></span>, and then return to the current file to continue checking. This process involves a lot of cross-file access, which makes concurrent processing more complex.

Our solution is to split the entire program into multiple parts and then let multiple type checkers work in parallel. Currently, we default to dividing the codebase into 4 parts (this number may be adjusted).

  • We create 4 type checkers, each of which can access the whole program,
  • But each checker only checks the files it is responsible for,
  • This way, they can perform type checking in parallel without needing to frequently access across files.

The benefits of this approach are:

  1. Parallel acceleration: The speed of type checking has increased by 3 times.
  2. Additional memory overhead is minimal: Although some type information may be redundantly computed in different checkers, the overall memory usage is still lower than the old compiler.
  3. Ultimately achieving a 10x performance improvement: The new Go compiler itself is already 3 times faster than the old TypeScript compiler, and with the concurrency optimization further increasing speed by 3 times, we ultimately achieve a 10x overall performance improvement.

Host: This is indeed a huge change! Looking ahead, what impact do you think this will have on the development of TypeScript over the next 10 years? Will it affect the design of language features?

Anders: I believe this is indeed an important turning point.

If we had done a similar restructuring 2-3 years after TypeScript was launched, the entire ecosystem might not have been ready yet, and we ourselves might not have had enough experience to make such changes. But now, TypeScript and ECMAScript have matured, and the pace of JavaScript language development has slowed down significantly compared to the past.

For example, from ES5 to ES6, we experienced a huge transformation, such as the addition of classes, arrow functions, modules, etc. But now, the evolution of the JavaScript language has clearly slowed down, and developers are increasingly focused on scalability and performance, rather than new type system features.

Of course, we will continue to follow the ECMAScript specification and may introduce some type system features. But I think the more important thing is: how do we leverage the 10x faster type checker.

  • Combining AI and generative programming,
  • Providing more high-precision code analysis,
  • Even real-time validation of the correctness of AI-generated code, ensuring it is not only syntactically correct but also semantically correct.

In the future, we may allow AI code generators (like LLMs) to directly call the TypeScript compiler to detect and correct errors in real-time. This way, we can not only generate code but also make AI-generated code safer and more reliable. This is undoubtedly an exciting direction!

In a real-time manner, we can not only check whether the AI-generated code is syntactically correct but also ensure its semantic correctness. If you plan to let AI generate code and actually deliver it to run in a production environment, this is crucial.

Yes, who can guarantee that this code is safe? The only way to keep AI “honest” is to check it through a deterministic type checker or validator.

I believe there are indeed some very interesting new directions that we previously had no conditions to explore, but now we can start seriously considering these possibilities.

Host: So, at some point in the future, do you think it is possible to have a runtime that natively supports TypeScript? I have been looking forward to this for many years. Of course, we now have Deno, which is written in Rust; perhaps this work will intersect with it. Do you think it is possible to build a runtime centered around TypeScript based on this codebase in the future?

Anders: In this industry, I have learned one thing: never say “impossible.” It is indeed possible.

However, I want to say that some performance bottlenecks of the current JavaScript runtime, such as JIT (Just-In-Time) compilation, which is part of the V8 runtime, could potentially be bypassed with a more native compilation system. However, the object model of JavaScript itself is also a challenge.

For example, JavaScript allows you to dynamically add new properties to objects (extend properties) or compute property names at any time. In fact, JavaScript objects are more like hash tables than structures in C; their arrangement in memory is entirely different. Although we might think they are similar, their behaviors are fundamentally different.

The way JavaScript handles numbers is also similar: it does not have true integers; all numbers are floating-point. If we want to maintain the semantics of JavaScript, these features cannot be completely discarded.

Of course, you can envision a language similar to TypeScript but with different semantics; many people have tried to do this and can build a native compiler for it. But the question is: is this really what people need? It is hard to say.

So, I am not sure how the future will develop. I once hoped to find some “magic powder” that would increase the performance of the JavaScript runtime by an order of magnitude, but honestly, I believe that at least in the foreseeable future, this situation will not happen. But who knows?

Host: You make a lot of sense. I am a technology enthusiast, so I often dream about these things; don’t blame me (laughs).

Last question. When I see some tools migrating to Rust, there are sometimes concerns in the community, although the word “opposition” may be too strong. Some people worry that many developers who were willing to contribute code using TypeScript and JavaScript might leave when the tools migrate to Rust, or they might be forced to learn Rust. You could say that this situation could also happen in TypeScript or Go.

However, I personally think this is actually a positive change. There is a lot of energy between different technology communities; we have already seen the success of the Rust ecosystem, and the Zig ecosystem (like Bun) is also showing similar trends. This shows that people are willing to step out of their comfort zones for projects they care about. But I want to know if this is a consideration for you? Have you considered the impact on third-party contributors?

Anders: Of course, the number of developers who understand both Go and JavaScript is certainly fewer than those who only understand JavaScript. But on the other hand, the number of developers willing to contribute to compiler development has always been limited; they are usually very interested in low-level technologies and many have experience in native development.

Moreover, migrating from JavaScript to Go is actually a relatively gentle process; Go is not a particularly complex or ritualistic language, while Rust is closer to C++, with a steeper learning curve. In contrast, Go is more like a modernized Python or JavaScript.

Host: When I wrote Go code, I worked as a Go developer for two years at a company. During an all-hands engineering meeting, some engineers complained that Go was “mediocre” and they did not like that Go could not do some “fancy” things. However, I will never forget the CTO’s response at that time. He told everyone, “You must understand that Go’s ‘mediocrity’ is intentional design.”

Anders: Go does not try to become complex; it pursues simplicity. But its results are not mediocre.

Host: For example, Kubernetes is definitely not a “mediocre” software project. The Go language enables us to build large and powerful systems like Kubernetes, which is a success in itself.

Host: Awesome! I am very excited about the next phase of this project. I think this is a great decision, and you have considered many aspects when making decisions, including third-party contributions, ecosystem compatibility, and so on. I am glad to see you taking this project so seriously.

I have always been a loyal fan of this team, and it is truly amazing that this project has developed to where it is today, with such powerful features and still growing. We are about to usher in a brand new chapter. Congratulations!

Anders: Thank you! I can tell you that our entire team is very excited about this. This is a huge motivation for us, and I believe it will be for the community as well. I think this will usher in another exciting decade for TypeScript, and we are ready to embrace this journey.

Host: Thank you for sharing today! This information is very valuable for library authors and core developers of the TypeScript ecosystem.

Anders: My honor, thank you for inviting me!

More references:

Microsoft Ports TypeScript to Go for 10x Native Performance Gains[7]

Microsoft Rewriting TypeScript in Go[8]

TypeScript Announces Go Rewrite, Achieves 10x Speedup[9]

References[1]

A 10x Faster TypeScript: https://devblogs.microsoft.com/typescript/typescript-native-port/

[2]

typescript-go: https://github.com/microsoft/typescript-go

[3]

Why Go?: https://github.com/microsoft/typescript-go/discussions/411

[4]

Full content: https://www.youtube.com/watch?v=10qowKUW82U

[5]

esbuild: https://github.com/evanw/esbuild

[6]

SWC: https://github.com/swc-project/swc

[7]

Microsoft Ports TypeScript to Go for 10x Native Performance Gains: https://visualstudiomagazine.com/Articles/2025/03/11/Microsoft-Ports-TypeScript-to-Go-for-10x-Native-Performance-Gains.aspx

[8]

Microsoft Rewriting TypeScript in Go: https://www.reddit.com/r/golang/comments/1j8shzb/microsoft_rewriting_typescript_in_go/

[9]

TypeScript Announces Go Rewrite, Achieves 10x Speedup: https://www.totaltypescript.com/typescript-announces-go-rewrite

Microsoft Rewrites TypeScript in Go, Achieving 10x Performance Boost

  • 《AI数据分析之ChatBI发展与应用实践》白皮书(附下载)正式上线啦

  • Linux 一键巡检脚本,建议收藏!

  • MySQL要坐不住了!Vitess之父Sugu“投敌”Postgres造新数据库,这次真要掀翻桌子?

  • 为什么DeepSeek火之后,人们想到的是大量裁员,而不是实行上三休四?

  • 苹果“痛下杀手”弃Java,用自家语言Swift重写关键服务:内存减90%,性能增40%!

  • 号外!《核心系统分布式数据库选型指南》电子书(附下载)正式上线

  • 解锁数据架构现代化密码,《实时数仓选型指南》电子书(附下载)正式上线啦

Leave a Comment