BENCHMARKS

OpenUI Rewrites Rust WASM Parser in TypeScript, Achieves 3x Speed Gain

J James Whitfield Mar 21, 2026 Updated Apr 7, 2026 4 min read
Engine Score 7/10 — Important

This story presents a surprising performance gain by rewriting a Rust WASM parser in TypeScript, offering valuable insights for developers considering language choices for performance-critical components. Its high novelty and actionability make it an important technical case study for a specific segment of the industry.

  • OpenUI’s Thesys engineering team rewrote their Rust WASM parser in TypeScript and achieved 2.2x to 4.6x faster per-call performance, with 2.6x to 3.3x lower total cost in streaming scenarios.
  • The performance bottleneck was not Rust‘s speed but the WASM boundary tax: mandatory serialization and memory copying between the JavaScript heap and WASM linear memory on every call.
  • An algorithmic improvement from O(N squared) to O(N) streaming via statement-level incremental caching delivered larger gains than the language switch itself.

What Happened

The Thesys engineering team at OpenUI published a detailed analysis on March 13, 2026, explaining why they rewrote their Rust-based WASM parser entirely in TypeScript. The parser, which processes structured UI output from language models for React rendering, ran faster after the rewrite despite moving from a compiled language to an interpreted one.

In benchmarks across three test fixtures measured over 1,000 runs each, the TypeScript version was 2.2x faster on simple inputs (180 characters, 9.3 microseconds vs. 20.5 microseconds), 4.6x faster on medium inputs (400 characters), and 3.0x faster on large inputs (950 characters). The team described the result as counterintuitive but attributable to a well-understood cause.

Why It Matters

The common assumption that compiling to WASM automatically delivers better performance than JavaScript does not hold in all cases. The OpenUI team’s findings demonstrate that when a function is called frequently on small inputs and returns complex JavaScript objects, the overhead of crossing the WASM boundary can exceed the computational savings from running native code. The post has generated significant discussion in the developer community, reaching the front page of Hacker News.

The team’s key insight: “The Rust parsing itself was never the slow part. The overhead was entirely in the boundary.” Every WASM call required copying strings from the JavaScript heap to WASM linear memory, serializing results to JSON within WASM, copying the JSON back to the JavaScript heap, and deserializing via V8’s JSON.parse.

Technical Details

The parser uses a six-stage pipeline: an autocloser that makes partial text syntactically valid, a single-pass lexer, a token splitter, a recursive-descent parser that builds an AST, a resolver that inlines variable references with circular reference detection, and a mapper that converts the internal AST to the output format for React.

The team first attempted to optimize the Rust version using serde-wasm-bindgen, which returns JavaScript objects directly instead of serializing to JSON. This approach performed 29% worse on medium inputs. The reason is that constructing native JavaScript objects from Rust requires serde-wasm-bindgen to recursively materialize each field through hundreds of fine-grained boundary crossings, whereas JSON serialization runs entirely in Rust with a single memory copy.

Beyond the per-call improvement, the team implemented statement-level incremental caching for streaming scenarios. In the naive approach, each new LLM output chunk triggers a full re-parse of the entire accumulated text, creating O(N squared) complexity. For a 1,000-character output arriving in 20-character chunks, that means 50 parse calls processing approximately 25,000 cumulative characters.

The incremental approach recognizes that statements terminated by depth-zero newlines are immutable once complete. The parser caches these finished statements and only re-parses the trailing incomplete statement on each new chunk, reducing complexity from O(N squared) to O(N). This cut total streaming cost by 2.6x on the contact form fixture and 3.3x on the dashboard fixture.

Who’s Affected

The findings are directly relevant to any team using WASM for parser or serialization workloads in browser or Node.js environments. Front-end teams building LLM-powered interfaces that parse streaming model output face the same boundary cost problem that OpenUI encountered.

The post identifies valid WASM use cases as compute-bound work with minimal JavaScript interop, such as image processing, cryptography, physics simulations, and porting native libraries like SQLite to the browser. Frequently-called functions on small inputs that return structured JavaScript objects are poor candidates. Teams considering a Rust-to-WASM approach for similar parsing tasks should profile boundary overhead before committing to the architecture.

What’s Next

The team noted that the algorithmic improvement from O(N squared) to O(N) streaming had a larger practical impact than the language switch. This suggests that profiling actual bottlenecks before selecting an implementation language is more productive than assuming a faster language will produce a faster result. The full analysis with benchmark data is available on the OpenUI engineering blog.

Related Reading

Share

Enjoyed this story?

Get articles like this delivered daily. The Engine Room — free AI intelligence newsletter.

Join 500+ AI professionals · No spam · Unsubscribe anytime