# Performance Implications of Using TypeScript-Go: A Deep Dive into Compilation Speed and Memory Usage

> Explore TypeScript-Go performance implications. Discover faster compilation and efficient memory usage thanks to Go's speed and incremental builds. Analyze telemetry for insights.

- Repository: [Microsoft/typescript-go](https://github.com/microsoft/typescript-go)
- Tags: deep-dive
- Published: 2026-04-25

---

**TypeScript-Go delivers comparable compile-time accuracy to the original `tsc` while leveraging Go's low-latency startup and efficient memory handling, offering significant speedups through incremental builds and detailed performance telemetry.**

TypeScript-Go is Microsoft's native Go implementation of the TypeScript 6.0 compiler, hosted in the `microsoft/typescript-go` repository. This rewrite reimagines the compilation pipeline using Go's runtime characteristics, providing developers with granular visibility into build performance through built-in statistics and memory accounting. Understanding the performance implications of using typescript-go requires examining its single-pass architecture, incremental snapshot system, and goroutine-based telemetry.

## Architectural Performance Characteristics

The compiler's performance profile is shaped by architectural decisions that prioritize deterministic execution and low overhead.

### Single-Pass Compilation Pipeline

At the core of TypeScript-Go is a linear compilation pipeline defined in [`internal/execute/tsc/compile.go`](https://github.com/microsoft/typescript-go/blob/main/internal/execute/tsc/compile.go). The compiler executes a strict sequence: **config → parse → bind → check → emit**. This single-pass approach minimizes context switching and keeps the critical path simple.

Each phase records its duration in a `CompileTimes` struct (lines 65-73 of [`compile.go`](https://github.com/microsoft/typescript-go/blob/main/compile.go)), enabling precise bottleneck identification. The parser reuses scanner logic from the original TypeScript implementation, ensuring exact syntax compatibility while reading files through a virtual file system (`vfs.FS`). Binding and checking phases include short-circuiting optimizations—such as the early exit logic at line 11337 in [`checker.go`](https://github.com/microsoft/typescript-go/blob/main/checker.go)—that avoid unnecessary type-of checks during the checker phase.

### Incremental Build Optimization

For large monorepos, TypeScript-Go implements incremental compilation via `ReadBuildInfoProgram` in [`internal/execute/incremental/incremental.go`](https://github.com/microsoft/typescript-go/blob/main/internal/execute/incremental/incremental.go). When `--incremental` is enabled, the compiler reads a `.tsbuildinfo` snapshot and transforms it into a program state (lines 44-55), reusing previously computed type information.

This approach restricts reprocessing to changed files only, often reducing parse/bind/check time by over 80% on subsequent builds. The build-info file maintains references to unmodified source files, allowing the compiler to skip entirely the parse and bind phases for stable code.

### Memory Management and GC Impact

Memory usage is tracked through Go's `runtime.MemStats` after each compile cycle, as implemented in [`internal/execute/tsc/statistics.go`](https://github.com/microsoft/typescript-go/blob/main/internal/execute/tsc/statistics.go). The system reports `memoryAllocs` and `memoryUsed` metrics, providing insight into allocation patterns and heap utilization.

While the Go runtime's garbage collector can introduce latency spikes, the overall memory footprint typically remains below that of the native `tsc` for comparable inputs. The emitter phase—which generates JavaScript and declaration files—can be disabled entirely using `--noEmit`, cutting runtime by approximately 30% on large codebases by eliminating associated heap allocations and file I/O.

## Runtime Performance by Mode

TypeScript-Go ships as a versatile toolchain supporting CLI, Language Server Protocol (LSP), and HTTP API modes, each with distinct performance characteristics.

### Command-Line Compilation

The entry point at [`cmd/tsgo/main.go`](https://github.com/microsoft/typescript-go/blob/main/cmd/tsgo/main.go) (lines 23-24) dispatches arguments to `execute.CommandLine`. When `--diagnostics` is passed, the CLI prints a human-readable statistics table via the `Statistics.Report` method in [`internal/execute/tsc/statistics.go`](https://github.com/microsoft/typescript-go/blob/main/internal/execute/tsc/statistics.go).

Output includes phase-specific timings:
- **Config time**: Duration spent loading [`tsconfig.json`](https://github.com/microsoft/typescript-go/blob/main/tsconfig.json) (note that [`tsconfigparsing.go`](https://github.com/microsoft/typescript-go/blob/main/tsconfigparsing.go) lines 1400-1405 document potential polynomial performance in complex configuration scenarios)
- **Parse time**: Duration of AST generation
- **Check time**: Type-checking duration
- **Emit time**: File writing duration

This granular reporting allows developers to identify whether projects are bound by I/O, type-checking complexity, or emission overhead.

### Language Server Performance

Running `tsgo --lsp` initiates a persistent server defined in `internal/lsp` that reuses the same program instance across requests. Unlike the CLI mode, which constructs and destroys the program on each invocation, the LSP maintains state between file changes, providing sub-second response times for hover, completion, and diagnostics.

Long-running processes emit performance telemetry every five minutes via a dedicated goroutine defined in [`internal/project/session.go`](https://github.com/microsoft/typescript-go/blob/main/internal/project/session.go) (constants at lines 481-483). This ticker gathers CPU, memory, and custom metrics with negligible overhead, enabling early detection of performance regressions in IDE integrations.

### Watch Mode Limitations

The prototype watcher in [`internal/execute/watcher.go`](https://github.com/microsoft/typescript-go/blob/main/internal/execute/watcher.go) triggers a `DoCycle` on file changes. However, the current implementation **rebuilds the entire project** without leveraging the incremental snapshot system. While acceptable for small-to-medium codebases, this behavior creates a bottleneck for very large projects where full type-checking dominates the cycle time. Future iterations are expected to integrate incremental rechecking into the watch loop.

## Optimization Strategies

| Situation | Recommended Configuration | Expected Impact |
|-----------|---------------------------|-----------------|
| Large codebase with many unchanged files | Enable `--incremental` | 80%+ reduction in parse/bind/check time |
| CI pipelines requiring type-checking only | Use `--noEmit` | ~30% faster execution, reduced memory allocation |
| Debugging slow builds | Add `--diagnostics` | Phase-specific timing breakdown |
| IDE integration | Start with `--lsp` | Eliminates per-request startup overhead |

## Code Examples

### Measuring Compile Performance

Capture detailed statistics programmatically:

```go
sys := newSystem() // implements internal/execute/tsc.System interface
args := []string{"src/index.ts", "--diagnostics"}
result := execute.CommandLine(sys, args, nil)
// Statistics table printed via sys.Writer includes:
// Files: 124, Lines: 3452, Parse time: 0.123s, Check time: 0.567s

```

### Incremental Build Workflow

```bash

# Initial build creates .tsbuildinfo

tsgo -p tsconfig.json --incremental

# Subsequent builds reuse the snapshot from internal/execute/incremental/incremental.go

tsgo -p tsconfig.json --incremental

```

### Language Server Startup

```bash

# Reuses program state across requests; telemetry emitted every 5 minutes

tsgo --lsp

```

## Summary

- **TypeScript-Go** implements a single-pass pipeline (`config → parse → bind → check → emit`) instrumented via `CompileTimes` in [`internal/execute/tsc/compile.go`](https://github.com/microsoft/typescript-go/blob/main/internal/execute/tsc/compile.go)
- **Incremental builds** using `ReadBuildInfoProgram` in [`internal/execute/incremental/incremental.go`](https://github.com/microsoft/typescript-go/blob/main/internal/execute/incremental/incremental.go) reduce rebuild times by over 80% for large projects
- **Memory accounting** via `runtime.MemStats` in [`internal/execute/tsc/statistics.go`](https://github.com/microsoft/typescript-go/blob/main/internal/execute/tsc/statistics.go) provides visibility into heap usage and allocation rates
- **Watch mode** currently performs full rebuilds rather than incremental updates, pending optimization
- **LSP mode** eliminates startup overhead by reusing program instances and emits telemetry via [`internal/project/session.go`](https://github.com/microsoft/typescript-go/blob/main/internal/project/session.go)
- **Performance diagnostics** are available via `--diagnostics`, reporting phase-specific durations and memory consumption

## Frequently Asked Questions

### How does TypeScript-Go's compilation speed compare to the original tsc?

TypeScript-Go achieves comparable compile-time accuracy while leveraging Go's faster startup and lower memory overhead. The single-pass pipeline in [`internal/execute/tsc/compile.go`](https://github.com/microsoft/typescript-go/blob/main/internal/execute/tsc/compile.go) eliminates JavaScript engine warmup costs, and incremental builds via `--incremental` provide dramatic speedups—often exceeding 80% time reduction—on subsequent compilations of large codebases.

### What causes high memory usage in TypeScript-Go builds?

High memory consumption typically occurs during the emit phase when generating JavaScript and declaration files. Running with `--no Emit` reduces runtime by approximately 30% and lowers heap allocations. The `Statistics` struct in [`internal/execute/tsc/statistics.go`](https://github.com/microsoft/typescript-go/blob/main/internal/execute/tsc/statistics.go) reports `memoryUsed` and `memoryAllocs` via Go's `runtime.MemStats`, allowing identification of allocation-heavy phases.

### Why is watch mode slower than incremental compilation?

The prototype watcher in [`internal/execute/watcher.go`](https://github.com/microsoft/typescript-go/blob/main/internal/execute/watcher.go) triggers full project rebuilds via `DoCycle` rather than utilizing incremental snapshots. Unlike `--incremental` mode which calls `ReadBuildInfoProgram` to load state from `.tsbuildinfo`, the watcher currently reparses and rechecks all files regardless of changes. This limitation affects very large projects most significantly.

### How can I monitor TypeScript-Go performance in production?

Enable `--diagnostics` for CLI builds to receive phase-specific timing tables. For long-running processes like the Language Server, performance telemetry automatically aggregates every five minutes in [`internal/project/session.go`](https://github.com/microsoft/typescript-go/blob/main/internal/project/session.go) (lines 481-483), reporting CPU and memory metrics via non-blocking goroutines with minimal runtime overhead.