# TypeScript in Rust: What tsrs Changes About Type Checking

Fast JavaScript builds can hide a slower feedback loop. A development server may update the browser almost immediately while the editor is still calculating whether a change is type-correct. On a large TypeScript project, producing runnable code and understanding that code can have very different costs.

The Rust-based compiler project `tsrs` targets the second problem. It ports the behavior of Microsoft's native TypeScript implementation into Rust, with the goal of faster checking and lower memory use. Early comparisons show substantial improvements, but the useful story is broader than a contest between programming languages.

**A faster checker matters only if it preserves the behavior a project depends on.** Understanding `tsrs` therefore requires looking at its benchmark workload, its compatibility evidence, and the difference between a promising compiler implementation and a complete development workflow.

<iframe class="aspect-video h-auto" width="100%" height="315" src="https://www.youtube.com/embed/Quf05tmWjaQ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

## Type checking and code generation solve different problems

TypeScript adds a static type system to JavaScript. Before a program runs, a checker can analyze relationships among values, declarations, and function calls and report combinations that violate the configured rules. The JavaScript runtime does not enforce those TypeScript annotations after they have been removed.

Consider a function that expects a numeric identifier. A call that passes a string can still be represented as JavaScript, even though TypeScript should reject that call during checking. Generating executable output and deciding whether the call is valid are separate operations.

The [TypeScript handbook](https://www.typescriptlang.org/docs/handbook/2/basic-types.html) explains this distinction, including why compiler settings can determine whether errors prevent output generation. A successful transformation into JavaScript does not, by itself, establish a clean type check.

Tools can deliberately optimize for the transformation task. For example, [esbuild's TypeScript documentation](https://esbuild.github.io/content-types/#typescript) states that it removes type annotations without performing type checking. This is why a project can have an extremely fast bundling pipeline and still need a separate checker in the editor or continuous integration.

The expensive work involves relationships across the program. Imported definitions, inferred generic types, conditional types, and unions can require substantially more analysis than removing annotations from a file. Counting source lines captures only part of that workload. Two projects of similar size can put very different pressure on a checker.

## tsrs ports an existing implementation

Microsoft's native TypeScript work provided the Go implementation that `tsrs` used as its behavioral reference. This is an important distinction from inventing a new language or designing a type system from scratch. A port changes the implementation language while trying to preserve observable behavior.

The existing design provides a structure to follow: functions, data relationships, evaluation order, and expected diagnostics. That structure makes progress easier to evaluate than a broad request to build a compatible compiler with no reference implementation.

Max Schwenk's [account of the initial port](https://building.owner.com/tsrs) describes directing Claude agents to preserve the Go implementation rather than redesign it. He reports writing no implementation code himself, while using tests and profiles to direct the work. The first working checker was a starting point for further compatibility and memory improvements, not proof that the finished maintenance problem had disappeared.

The short initial development period also rests on work that preceded the Rust repository. TypeScript's language design, Microsoft's native implementation, and the accumulated test corpus were already available. Comparing the calendar time of the port with the time spent developing the upstream implementation does not compare equivalent engineering tasks.

![Timeline contrasts the reported initial Rust port period with the longer upstream implementation effort](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/8fb22517-49fb-43c2-7b80-3ec39a4ff200/lg2x =1920x1080)

That is still a meaningful use of coding agents. A mature reference narrows the space of acceptable answers, and automated feedback exposes deviations quickly. The value comes from preserving and testing a known behavior while changing how the implementation represents and executes it.

## The benchmark shows a particular workload

Better Stack's comparison used the TypeScript repository as a test project, reporting 793 files and roughly 380,000 lines of code. It compared the earlier JavaScript-based compiler, the native Go compiler, and `tsrs` across four conditions: a valid project, one simple type error, a broken central type, and an excessively deep recursive type.

![TypeScript repository selected as the source workload for the compiler comparison](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/3cc730f0-d19d-4cbe-0a28-1a4429e70000/lg1x =1920x1080)

The reported timings below are rounded descriptions of that comparison. They apply to its test setup and should not be read as a promise for other repositories or current compiler releases.

| Test condition | JavaScript compiler | Go compiler | Rust compiler |
| --- | ---: | ---: | ---: |
| All types valid | About 7 seconds | About 0.7 seconds | About 0.3 seconds |
| One simple type error | About 6.6 seconds | About 0.6 seconds | About 0.2 seconds |
| Broken central type | About 13 seconds | About 2.2 seconds | About 0.5 seconds |
| Recursive type too deep | About 7 seconds | About 0.8 seconds | About 0.4 seconds |

The comparison summarized the Go implementation as roughly nine to ten times faster than the JavaScript implementation, and Rust as roughly 24 times faster than JavaScript and 2.8 times faster than Go. Those summary figures come from the comparison's underlying results. Recalculating ratios from the rounded table would discard precision and could produce different aggregates.

![Reported average speedups across the four compiler comparison scenarios](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/89db69ef-d08a-4c26-06c1-f7c595e89400/public =1920x1080)

The absolute differences help explain the practical effect. In the valid-project case, moving from several seconds to well below a second changes how a full check feels. Moving from around seven-tenths to three-tenths of a second is another improvement, but a smaller absolute saving. A development task includes more than that invocation, so its total duration will not improve by the same multiple automatically.

![Valid-project comparison reports 6.86 seconds for JavaScript, 0.67 for Go, and 0.28 for Rust](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/d9afdd05-f208-4dab-ea9c-3f9c6daec800/lg2x =1920x1080)

The single-error case still requires substantial program analysis even though the final diagnostic output is small. Its result belongs to the same repository and compiler comparison.

![Single-error benchmark reports 6.56 seconds for JavaScript, 0.59 for Go, and 0.24 for Rust](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/78ce24af-10f4-4895-6346-d7f92875bd00/lg2x =1920x1080)

The broken central type is especially informative. A widely used definition can propagate errors into many other locations, increasing work in both analysis and diagnostic generation. A checker needs to remain useful when a project is temporarily inconsistent, because that is a normal condition during editing.

![Broken-central-type comparison reports 12.98 seconds for JavaScript, 2.18 for Go, and 0.47 for Rust](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/b4e4431e-1af8-4141-2496-f3c1c90de800/md2x =1920x1080)

## Benchmark settings belong beside the result

A compiler benchmark compares more than the words Go and Rust. It compares particular versions, settings, worker counts, cache states, dependencies, and source revisions on a particular machine. If one implementation uses more cores or skips different work, the result can be valid for that configuration without being a controlled implementation comparison.

Cloudflare's [announcement of tsrs](https://blog.cloudflare.com/typescript-rust-port) reported early results of up to eight times faster checking and about 70% lower memory use against TypeScript 7.0 with equal type-check worker counts. It also reported substantial improvements in TypeScript 7.1 beta, including optimizations contributed upstream. These are project-reported results against named versions, separate from Better Stack's four-scenario comparison.

The current [benchmark methodology](https://github.com/oxc-project/tsrs/blob/main/bench/README.md) makes the configuration explicit. It records project revisions, compiler identities, arguments, resource settings, and samples. It also distinguishes default behavior from fast-mode runs. Those details make the results interpretable and help prevent a historical chart from becoming an unqualified claim about every release.

For a team's own workload, the relevant comparison should preserve the intended checking behavior and include representative error states. A configuration that reports fewer errors because it does less checking is a different tradeoff from one that reaches equivalent results sooner. Measuring correctness-related outputs alongside time helps separate those cases.

The recursion case exercises a different failure path from a simple mismatched value. Its timing is evidence for that input and configuration, not for every recursive type.

![Deep-recursion comparison reports 6.80 seconds for JavaScript, 0.81 for Go, and 0.44 for Rust](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/c6f56182-c4db-4250-ede7-ae50d5c98600/lg2x =1920x1080)

Memory deserves its own measurement. A lower peak footprint can allow more checks to run concurrently or avoid pressure on a developer machine, even when the time improvement is modest. Conversely, a faster configuration that consumes substantially more memory can reduce the number of useful concurrent jobs on a constrained runner.

## Compatibility needs more than matching error counts

The four-scenario comparison reported that the Go and Rust implementations both produced 26,054 errors in the broken-central-type scenario. That is a useful observation, but equal counts are not enough to establish equal behavior. Two tools could report the same number of diagnostics against different expressions or with different meanings.

A stronger comparison examines the diagnostic codes, messages, source locations, and relevant ordering. It also checks whether the process exits appropriately and whether generated output matches when emission is part of the contract. Compatibility is a collection of observable properties, not a single pass percentage.

![Conformance baseline graphic connecting Go checker functions to their Rust counterparts](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/97c6a3af-52f9-4133-1d1c-9c14502d6000/md2x =1920x1080)

The [current compatibility documentation](https://github.com/oxc-project/tsrs/blob/main/docs/COMPATIBILITY.md), checked on November 8, 2026, targets a pinned TypeScript 7.1 beta reference. It records matching results across thousands of diagnostic, type, symbol, and output baselines while also documenting known gaps. Its fast preset has additional qualifications around checker history and depth limits. Full conformance remains the project's stated goal rather than an unconditional guarantee for every program and configuration.

That is more precise than describing the compiler as simply fully compatible. Tests provide evidence within their covered cases. They cannot prove that every possible combination of types, files, module settings, and editor requests behaves identically. A project's real source code supplies another important test population.

The reference version also matters. A port can match one upstream revision closely while newer TypeScript behavior continues to evolve. Teams evaluating it need to know which language and compiler revision they are comparing against, especially if they depend on recently changed diagnostics or APIs.

## Rust provides options, not an automatic speed multiplier

Rust gives implementers direct control over allocation and object layout without requiring a tracing garbage collector. That creates opportunities to represent compiler data differently, but it does not guarantee that the first translation will use less memory or perform less work.

Schwenk's development account is explicit about this distinction: an early working port was faster on the target workload but initially used more memory than the Go implementation. Subsequent optimization changed how data was stored and when work was performed. The outcome depended on implementation decisions and measurement, not just changing the file extension.

This also explains why improvements can flow back to Go. Avoiding redundant work or using a better cache can benefit more than one implementation language. A comparison between evolving compilers is therefore a moving target, especially when the teams exchange findings.

The portability motivation extends beyond native executables. Cloudflare identifies WebAssembly and constrained development environments as reasons to pursue Rust. WebAssembly offers a way to execute compiled code inside a host environment, including browser-based tools. A compiler still needs memory, source files, and host integration; compiling to Wasm does not make those dependencies disappear.

The diagram illustrates the runtime distinction. It is not a measurement of compiled artifact sizes or a claim that Rust programs require no memory management.

![Conceptual WebAssembly comparison shows Go's garbage collector alongside the Rust implementation](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/67d76878-8902-4d8d-0cfb-5d8a350d6600/md2x =1920x1080)

The architectural opportunity is to place type checking closer to an interactive environment without requiring an entire conventional desktop toolchain. That could make a browser workspace or isolated agent environment more capable. Whether it is practical depends on the whole integration, including file access, package data, startup cost, and the host's available resources.

## A faster compiler is one part of a development workflow

The [tsrs project](https://github.com/oxc-project/tsrs) describes a Rust replacement for the TypeScript compiler and language server. At the time of review, its README separates reference-default behavior from an opt-in `--fast` preset. That distinction should remain visible when evaluating it: the fastest supported configuration is not necessarily the configuration used to establish strictest parity.

For continuous integration, a full check on a clean checkout is relevant. For an editor, the important questions include how quickly diagnostics update after a small change and whether navigation, completions, and project loading behave as expected. A command-line benchmark does not directly measure those interactions.

Incremental behavior introduces another difference. Reusing work from an earlier check can change the balance between startup, parsing, dependency analysis, and type evaluation. A compiler that wins a clean full check may have a different margin when both implementations are already warm. Neither measurement substitutes for the other.

There is also a distinction between checking and type-aware linting. A linter may need type information to recognize mistakes that syntax alone cannot reveal. Reusing an efficient checker could make that analysis cheaper, but the resulting lint workflow has its own rules, integration, and compatibility surface. It should be evaluated as a toolchain feature rather than inferred from a single compiler timing.

The practical adoption question is therefore specific: does this release preserve the results and interfaces this project needs while improving the part of the workflow that is actually slow? Keeping those requirements explicit makes an experimental evaluation useful even if the team continues using the official compiler for its primary checks.

## Final thoughts

`tsrs` demonstrates how a mature implementation, a substantial conformance suite, and coding agents can make a language port progress quickly. The reported speed improvements are promising, particularly when type checking dominates a development loop or consumes too much memory on shared infrastructure.

**The durable benefit comes from faster equivalent work, supported by explicit compatibility and benchmark evidence.** Rust enables useful implementation and deployment choices, but versions, configurations, and workload still determine the result. Treating those details as part of the story gives developers a sounder basis for evaluating `tsrs` than a universal claim that one language is three times faster than another.
