Vibe coding head to head
For a long time I had an idea on my BLOG todo list: writing an article about using TypeScript together with either Duktape or QuickJS.
Duktape or QuickJS are embedable JavaScript implementations. So my idea is to use this to host TypeScript's compiler and avoid having to install Node.js at all.
Duktape was quickly discarded as it implements an older, incomplete version of JavaScript that doesn't match the modern ES2015+ features required by the TypeScript compiler.
Recently I got a license to for Claude Pro as well as a DeepSeek API. And I figure this could make an excellent use-case for comparing Claude vs DeepSeek.
For this comparison I am using Claude Code. Claude Code is Anthropic's AI-powered coding agent. It can search codebases, trace dependencies, create and edit files across a project, build new features, and execute multi-file refactors at scale — work that can save days of engineering effort.
Using Claude Code, I tested:
- Sonnet 4.6
- deepseek-v4-flash
I ran these through three iterations of essentially the same prompts.
v1
The first prompt just gave instructions on using QuickJS to run TypeScript compiler without using Node.js.
Both AI's took the approach of downloading QuickJS and TypeScript and embed them into a single binary executable.
-
DeepSeek used TypeScript's
ts.transpileModule()API — transpile only, no type checking. The C code (449 lines ofmain.c) embedded the TypeScript JS bundle viald -r -b binary, an ELF-specific trick that wraps the blob into an object file accessed through linker symbols. The Node.js shim was a ~50-line C string literal. -
Claude embedded the full
tsc.jsCLI bundle — the same thing that runs when you typenpx tsc. It used Python scripts (gen_embed.py) to convert JS files into C byte arrays at build time, which is portable across Linux and macOS. The shim was a proper 226-lineshims.jsfile, evaluated separately.
The verdict from v1 was decisive: Claude had the better implementation for real
use. Running actual tsc with full type checking and tsconfig.json support is
categorically more useful than transpile-only. DeepSeek's only genuine advantage is
zero build-time dependencies — no Python, no curl, no tar. Just gcc and make.
But both had issues. DeepSeek broke source maps (the sourceMapText from
transpileModule() is silently discarded), had a fixed 8KB options buffer that
truncates silently, and used technically undefined behavior to access the blob
size. Claude had a duplicate process.stdout declaration, used strlen(data)
instead of the JS string length in js_write_file, and hardcoded process.platform
to 'linux' even when built on macOS.
v2
Afterwards, I did some interactive prompting to improve the code in both solutions. But I have to admit that I did more meddling with the DeepSeek codebase as compared to Claude's.
DeepSeek ballooned to 807 lines of C with a ~240-line
bootstrap JS string inlined as a C literal. It introduced a custom compiler host
that calls ts.createProgram() for type checking — a genuinely hard thing to do
correctly. The C↔JS communication went through JSON serialization.
But this is where things got ugly. The post-mortem identified six concrete bugs:
use_stdinis never set — declared and initialized to0, nothing ever sets it to1. The documented stdin transpile mode is completely dead code.- Fixed-size stack buffers (
paths_buf[16384],opts_buf[8192],lib_json[4096]) truncate silently, producing malformed JSON. - JSON injection risk — paths are inserted into JSON strings with a comment saying "no quotes/backslashes expected." Not enforced.
fexists()reads entire files — every existence check callstryLoad, which reads the whole file. Large.d.tsfiles get read twice.- Fragile JSON parsing —
strncmp(res_str, "{\"success\":true", 15)to check success. Breaks on whitespace changes. directoryExiststoo narrow — subdirectories always returnfalse, confusing TS module resolution.
Claude kept its clean three-layer design (C bindings → Node.js shims → tsc.js)
and grew a proper test suite with 10 tests. Its issues were cosmetic: an orphaned
gen_libs.py that generates a C lookup table for lib files but is never wired into
the Makefile, the same duplicate process.stdout, and the hardcoded platform string.
The v2 verdict: Claude remained the better-engineered project. DeepSeek's v2 had bugs that would cause real, observable failures. Claude v2's issues were cosmetic or edge-case.
v3
For v3, I started again, and installed on my laptop a QuickJS
version compiled by my distro and instructed the AI to simply use
that to run TypeScript tsc.js.
DeepSeek went "bundle-first" — it pre-concatenated the shim and
tsc.js into a single ~6 MB tsc-bundle.js. One command to run. Easy
for end users. But the shim quality regressed in critical ways:
-
crypto.createHashalways returns the same fixed string ("abcdef1234567890abcdef1234567890"for every input). This silently corrupts.tsbuildinfo— content hashes always match, so tsc's incremental cache sees files as never changed when they have. If you use--buildor incremental mode, you get wrong output and you don't know it. -
Buffer.from(data, encoding)ignores the encoding argument. Treats all strings as ASCII. Multi-byte UTF-8 content gets silently corrupted. -
process.envreads/proc/self/environat load time — Linux-only, breaks silently on macOS even though QuickJS runs there. Also snapshots the environment once, so runtime changes are invisible. -
path.join('/a', '/b')yields/a//binstead of/b— doesn't reset at absolute mid-arguments like Node.js does.
Claude took a different approach:
-
require('crypto')intentionally throws — this triggers tsc's fallback to its built-ingenerateDjb2Hash, which is a real hash function. Incremental builds work correctly because the author understood that sometimes the right implementation is "don't implement it, let the library handle itself." -
Bufferis a properclass _Buffer extends Uint8Arraywith full support for utf8, hex, base64, utf16le, ascii, and latin1 encodings. Integer read methods (readUInt8,readInt8,readUInt16LE, etc.) included. -
process.envis aProxybacked bystd.getenv()— lazy, on-demand, cross-platform, and always current. -
111 tests vs DeepSeek's 54, with unit-level coverage of Buffer, path, fs, os, process, require, and TextEncoder/TextDecoder — not just end-to-end integration smoke tests.
Claude's only real bug is a latin1 encoding mask that uses 0x7F instead of
0xFF, which corrupts high-byte characters. (Though in practice, tsc probably
never triggers it.)
The Pattern
Across all three versions, one pattern holds: Claude consistently makes the harder, more durable choice.
| Decision | DeepSeek's approach | Claude's approach |
|---|---|---|
| TS invocation | Custom host calling API methods | Runs real tsc.js unmodified |
| Shim embedding | Inline C string literal | Separate JS file, embedded at build time |
| Build deps | Zero (vendored everything) | Python, curl, tar (fetch at build) |
| Lib files | On disk at runtime | Embedded or found via search path |
| Crypto | Stubbed with fake hash | Intentionally throws → tsc fallback |
| Buffer | Minimal, encoding-ignorant | Full class, all major encodings |
| Testing | End-to-end smoke tests | Unit + integration (2x coverage) |
| Portability | Linux only (ELF blob) | Linux + macOS (C byte arrays) |
DeepSeek's projects are consistently easier to get started with — fewer build deps, simpler code, vendored everything. But they accumulate bugs faster because they're fighting against the complexity of the TypeScript API surface. Every new TypeScript feature that touches the runtime needs a shim update.
Claude's approach — let tsc.js run itself, provide a convincing Node.js facade, get
out of the way — is more work upfront (figuring out exactly which Node.js APIs tsc
touches is non-trivial) but pays off in maintenance. Upgrading TypeScript is just
dropping in a new tsc.js. The shim doesn't need to know anything about TypeScript
internals.
The Real Lesson
The most important takeaway isn't about TypeScript or QuickJS at all. It's about the shape of the problem.
DeepSeek tries to control TypeScript: wrap it in a custom host, serialize options as JSON, intercept its output. This works until TypeScript does something the host didn't anticipate.
Claude tries to become Node.js: provide the environment tsc expects, then get out of the way. This is more work initially but scales with the upstream.
DeepSeek's projects are fascinating engineering artifacts — compact, clever, audacious. Claude's projects are useful tools. There's a lesson there about knowing whether you're building a proof of concept or a product.
Cost comparisons
Here is a comparison of the costs for using DeepSeek's API platform versus a Claude Pro subscription.
The fundamental difference is that DeepSeek's API charges per token (pay-as-you-go) , while Claude Pro is a fixed monthly subscription for a chat interface with usage limits. Your choice depends entirely on your usage volume: DeepSeek is drastically cheaper per token, while Claude Pro offers predictability and access to more capable coding models within a cap.
| Feature | DeepSeek API (V4) | Claude Pro / Max Subscription |
|---|---|---|
| Pricing Model | Pay-per-token (consumption-based) | Fixed monthly fee |
| Lowest Cost | $0.0000035 per 1M input tokens (with cache + promotion) | $20 per month (flat rate) |
| Typical Cost | Input: $1 - $3 per 1M tokens Output: $2 - $6 per 1M tokens (for Pro model) |
$20/month (Pro) gives ~45 messages per 5 hours $100-200/month (Max) for higher limits |
| Best For | Low-volume testing, high-volume automated tasks, or when cost control is critical | Unlimited chat access within rate limits, predictable billing, and access to Claude's strongest models |
| Real-World Cost | ~$30-75/month for 1M input/1M output on DeepSeek V4 ~$450-900/month for same on Claude Sonnet 4.6 |
Light use: $20/month (stay within limits) Heavy API use: Developers report $500-2,000/month on Claude API |
Key Insights for Comparison
- Scale and Predictability: If you are a developer who uses AI coding assistance casually (e.g., a few dozen queries a day), the Claude Pro plan's fixed $20/month cost is predictable and simple. However, if you exceed the message limits, you'll be rate-limited. For heavy users, the Claude Max plan costs $100-200/month.
- Incredible DeepSeek Value: For any usage that requires processing millions of tokens (e.g., working with large codebases, document analysis, RAG applications), DeepSeek's API is dramatically cheaper. At the promotional cached rate of $0.025 per 1M tokens, you could process 800 million tokens for the same $20 as a single month of Claude Pro.
- Model Capability Trade-off: The price difference reflects a capability difference. For complex "agentic coding" tasks, Claude's Opus and Sonnet models (available via API or Pro subscriptions) are still considered the industry leaders, with DeepSeek noting its V4 code quality is close to but still behind Claude Opus 4.6. For most other tasks, DeepSeek V4 provides exceptional value at a fraction of the cost.
- API vs. Subscription Access: The most cost-effective approach for a heavy user might be to use the DeepSeek API via a pay-as-you-go service for most tasks and keep a Claude Pro subscription for complex problems requiring Claude's strongest models.
Final thoughts
So in general, in my opinion Claude is generally better at coding. DeepSeek is good enough, but one requires proper attention and supervision on what is doing. Where it makes a big difference is in pricing. DeepSeek is indeed a fraction of what Claude would cost otherwise.
Artifacts
You can find the generated code in my github repository here.
The final TypeScript based on Claude was tweaked and cleaned up and bundled into a repo on github here.