JOE: a freestanding JavaScript engine
I got my first programming job as a teenager, which led to landing some of the first commits to Deno. Nothing important, and I didn't stay long — life happened. A few years later I came back with the wrong expectations and spent a year building Deno Deploy. I'm not there anymore. I was let go, and the honest version is that I wasn't the engineer the company needed by the end. They wanted to ship fast and break things. I wanted to slow down and get the bytes right.
The way it got put to me, more than once, was that I like algorithms a little too much. It was meant as a gentle criticism and I took it as one. It also turned out to be portable: a while later, meeting someone for the first time in SF, they opened with "So — I hear you like algorithms!" I said, "So I see you've talked to Bert." We both laughed.
I've decided to stop apologizing for it. JOE is what "likes algorithms a little too much" looks like when you let it finish the sentence.
Performance is correctness
"Move fast and break things" is good advice for most software. In most products the code is a means to an end — the business lives somewhere above it, and the right move is to experiment quickly, ship, and learn. I believe that. I'd give the same advice to almost anyone.
A runtime is the exception, and the exception matters because of what the product is. In a runtime the code isn't a means to the product. The code is the product. The thing you are selling is the runtime's behavior, byte for byte. So the usual permission to be sloppy in the interior — "it's just plumbing, ship it" — doesn't apply, because there is no interior. It's all surface.
For a Node-compatible runtime, you can't win on correctness — Node is the oracle. The only leverage left is performance.
And I'd go further: performance is correctness, wearing different clothes.
That conviction didn't fit where I was. So I stopped arguing and started writing code.
JOE
JOE — JS Opt Engine — is a symbolic analyzer and optimizing engine for JavaScript, written as a freestanding Rust binary. Zero external dependencies. Allocators, collections, syscalls, threading, hashing, Unicode, math — all from scratch. It can run on Linux without libc.
That sounds like masochism, and partly it is. But it's the thesis applied to myself: if the runtime is the product, then its dependencies are part of the product too, and "we don't control that, it's a library" is not an answer I'm allowed to give. Owning the whole world is the only way to be responsible for the whole world's behavior. It's the same instinct that makes Go's toolchain feel coherent — not the single binary, but the refusal to treat anything as out of scope.
Numbers
Benchmarked against oxc, swc, and V8's d8 --no-lazy (eager parse). The 6-byte hello.js is baseline — it isolates startup cost.
| File | JOE | oxc | swc | V8 --no-lazy |
|---|---|---|---|---|
| hello.js (6 B) | 337 µs / 2.4 MB | 597 µs / 2.7 MB | 675 µs / 3.5 MB | 10.3 ms / 28 MB |
| react.js (642 KB) | 2.4 ms / 3.0 MB | 4.7 ms / 6.0 MB | 8.5 ms / 9.7 MB | 16.6 ms / 32 MB |
| − baseline | 2.1 ms | 4.1 ms | 7.8 ms | 6.3 ms |
| fabric.js (821 KB) | 3.3 ms / 3.2 MB | 7.0 ms / 7.7 MB | 12.1 ms / 12 MB | 19.1 ms / 34 MB |
| − baseline | 3.0 ms | 6.4 ms | 11.4 ms | 8.8 ms |
| ember.js (1.95 MB) | 5.9 ms / 4.2 MB | 12.5 ms / 12.8 MB | 25.6 ms / 21 MB | 29.9 ms / 42 MB |
| − baseline | 5.6 ms | 11.9 ms | 24.9 ms | 19.6 ms |
2x faster than oxc, on a third of the memory.
The other number is startup. Because JOE is freestanding — no libc init, no dynamic linker — the process is alive almost immediately:
| Program | Cold start |
|---|---|
joe version |
4.4 µs |
joe parse - 'console.log("hello world")' |
173 µs |
| C hello-world | 182 µs |
| Rust hello-world | 335 µs |
| Go hello-world | 890 µs |
JOE starts faster than a C "hello world." That isn't a trick; it's just what's left when you delete everything between _start and your code.
What this is not, yet
To be precise: JOE does not execute JavaScript yet. What exists today is the front end and the foundations: lexer, a near-spec-complete parser, the allocators, the freestanding runtime, the string interner. There is no interpreter, no JIT, no optimizer running end-to-end.
What's coming: an optimizer and an AOT backend. The goal is a compiler that embeds the runtime, not a runtime that embeds a compiler.
That's a long road and I'm walking it in the open. The parser is the first milestone, and it already says what I needed it to say.
Why I'm writing this
I'll open-source JOE once it runs something end-to-end. I'm writing now, before that, because I spent a year arguing that performance is correctness and that a runtime deserves a different standard of care, and I lost that argument in the place it mattered to me. This is me making it again — not with a diary entry this time, but with a parser that's twice as fast as the best one in its class and starts before a C program can clear its throat.
This is what Node or Deno might look like, built by someone who likes algorithms a little too much. I no longer think that's a flaw in the engineer — and the benchmarks make me suspect it's a feature of the runtime.
The work is the argument. Here it is.
I'm looking for work. If "likes algorithms a little too much" sounds like a feature to you: hey@parsa.wtf
Benchmarks were run with my hyperfine fork, which adds CPU pinning, post-fork/pre-exec timing, and memory stats via wait4.