cd /news/developer-tools/making-my-typescript-types-15-7x-fas… · home topics developer-tools article
[ARTICLE · art-30700] src=dev.to ↗ pub= topic=developer-tools verified=true sentiment=↑ positive

Making my TypeScript types 15.7x faster

A developer optimized TypeScript type inference in the Sury schema validation library, achieving up to 15.7x faster type extraction by reading a pre-resolved type field instead of matching the entire schema type. The fix reduced type instantiations for extracting S.Output from 7,842 to 501, and similar gains were seen across other operations. The optimization was guided by type instantiation benchmarks using @ark/attest and validated with type tests.

read4 min views1 publishedJun 17, 2026

If you opened this article, you probably already agree with me: sometimes TypeScript compiles painfully slowly.

The same thing happened to @_chenglou — an amazing dev who inspires me (he worked on React, Messenger, ReasonML and ReScript, and currently Midjourney, Pretext).

He's also a user of Sury — the fastest schema validation library, which I maintain — and the author of this issue: "Large TS types cause type inference slowdown".

That's... not nice. But I knew ArkType exists, and that people have built wild things in TS's type system (Doom included). So the slowness was clearly solvable, not fundamental. Here's the recipe I used. You can apply it to your own library.

Before we begin, here are the results. These are type-instantiation counts, measured with @ark/attest:

Operation Before After Faster
Define a 10-field object 1409 343 4.1×
Define a 5-field object (mixed optional) 17916 10557 1.7×
Define a 3-level nested object 31013 20419 1.5×
Define a union of 5 objects 67580 53173 1.3×
Extract S.Output (5-field object)
7842 501 15.7×
Extract S.Input (5-field object)
6767 501 13.5×
Extract S.Output (nested object)
7732 501 15.4×
Define + extract (10-field object)
9446 844 11.2×
Merge + extract output 13651 7039 1.9×

The title comes from that 15.7× on S.Output

. Extraction is the operation you hit most often: every type X = S.Output<typeof schema>

in your code pays for it.

Let's begin. The recipe is three steps.

If you want to change something, you first need a way to know you didn't break it. For a library like this, that means tests for types:

import { expectTypeOf } from "vitest";
import * as S from "sury";

const user = S.schema({ id: S.string, age: S.number });

// Pin the inferred type. If it drifts, the test fails.
expectTypeOf<S.Output<typeof user>>().toEqualTypeOf<{
  id: string;
  age: number;
}>();

I used to use ts-expect for this, but I migrated to

expectTypeOf

, above) to drop a dependency. Either way, I already had the tests, and I'll admit they really earned their keep. A type optimization can quietly turn { a: string }

into { a?: string }

and nothing throws. The tests are what catch that.If you want to improve something, you have to measure it first, or you can't actually tell whether you improved anything.

For this I used ** Attest** (

@ark/attest

) from the It counts the type instantiations an expression costs and pins them as a baseline:

import { bench } from "@ark/attest";
import * as S from "sury";

bench("define a user schema", () => {
  return S.schema({ id: S.string, age: S.number });
}).types([230, "instantiations"]);

Bonus: bake those baselines into CI, and any future regression will fail the build.

The third step was the easy one: I asked Claude to find the most impactful type-instantiation optimizations.

The fix turned out to be small. To read a schema's type back out, S.Output

/ S.Input

matched against the entire Schema<…>

type. That type is huge: a big object with several with

overloads, intersected with a union of every schema variant. So TypeScript had to expand all of it on every extraction, and the cost grew with the schema:

// Before: match the whole Schema<…> shape just to read Output back out
type Output<T> = T extends Schema<infer Output, unknown> ? Output : never;

But every Sury schema already carries a ~standard

field (the Standard Schema marker) that holds the resolved types. So the fix is to read that one slot directly, instead of re-deriving it from the whole shape:

// After: match only the one field that already holds the type
type Output<T> = T extends { "~standard": { types?: { output: infer Output } } }
  ? Output
  : never;

Now extraction costs the same no matter how big the schema is. That one change is most of the 11–16× you see above.

It depends (my favorite answer). But feedback loop speed matters more and more as AI iterates on your code. If your codebase's TS compilation is slow, you can use the same approach to identify the slow parts and optimize them.

One caveat: 15.7× is the drop in type instantiations, not seconds. I didn't time the actual compile. But instantiations are the work tsc

does while type-checking, so fewer of them is a fair proxy for what you feel in the editor. 🙂

Hope this was useful. Check out Sury — the most powerful schema library in the TS ecosystem. Follow me on X at @dzakh_dev, and ask your questions in the comments!

── more in #developer-tools 4 stories · sorted by recency
── more on @sury 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/making-my-typescript…] indexed:0 read:4min 2026-06-17 ·