apps/blog/content/blog/why-prisma-orm-checks-types-faster-than-drizzle/index.mdx
Type performance is just as important as runtime speed for developer experience. Slow types mean laggy editors, broken autocomplete, and long CI runs. This post benchmarks Prisma ORM and Drizzle ORM to show how their different approaches affect TypeScript compiler performance.
When we think about performance in our apps, we often think about runtime metrics: response times, cold starts, database query speeds. But TypeScript developers know that there's a whole other dimension to performance: type checking performance.
This is how efficiently the TypeScript compiler can analyze, evaluate, and validate your types during development. And when type performance degrades, developers will feel it immediately through laggy editors or slow CI processes:
tsc takes minutes to finish in CITypeScript is essentially doing runtime computations at compile time, especially with modern libraries that lean heavily on advanced type features. When your types become large or deeply recursive, TypeScript's job becomes exponentially harder. The result? A sluggish, frustrating developer experience.
In this article, we compare type performance of Prisma ORM and Drizzle ORM. Though both aim to provide type-safe access to your database, they follow fundamentally different philosophies:
prisma generate command and writes them to .d.ts files. Your editor simply loads these precomputed types. This approach builds on the Prisma Schema Language, a declarative way to define your data model that makes type generation possible.Here are the main differences and tradeoffs of the two approaches:
| Code generation (Prisma ORM) | Type inference (Drizzle) | |
|---|---|---|
| When types are created | During prisma generate — written to .d.ts files | On the fly — inferred by the TypeScript compiler whenever you write code |
| How the editor/CI sees them | Loads pre-generated .d.ts types | Recomputes types directly from your schema and queries |
| Impact on type-checking performance | Stable, since most computation happens ahead of time | More variable, since each query triggers fresh inference |
@ark/attestTo create our type checking benchmarks, we collaborated with TypeScript expert David Blass, the creator of ArkType, a type-safe runtime validation library.
David is well known in the TypeScript ecosystem for his full-time open-source work on advancing type system tooling and performance, and he built @ark/attest, the library we used to test and benchmark type-level logic.
The benchmark setup is available in an open-source repo on GitHub and has three main ingredients:
@ark/attest provides a way to write tests that measure type instantiations (the number of types the compiler needs to evaluate) and check time (how long it takes to type-check the file). This is more reliable than timing tsc runs alone because instantiations are deterministic across environments and TypeScript versions.
We used the classic Northwind schema as a realistic database model. Both Prisma and Drizzle benchmarks were reduced to only the type-relevant expressions so that runtime logic didn't interfere.
Each ORM was tested in two ways:
prisma.schema.bench.ts and drizzle.schema.bench.ts) measured the cost of checking the entire schema at once.prisma.query.bench.ts, drizzle.query.ts and drizzle.relational.bench.ts) measured the cost of evaluating types in realistic queries.For Drizzle, we included two variants because they represent different levels of abstraction:
drizzle.relational.bench.ts) with relational helpers. This is the closest conceptual equivalent to Prisma Client and the main point of comparison in our analysis.drizzle.query.ts) that maps more directly to SQL. It offers more flexibility but produces more complex type signatures, which typically stress the compiler more.The repository is structured so that each benchmark file contains one or more attest.bench blocks. For example, here is a simplified snippet from the Prisma query benchmarks:
import { bench } from "@ark/attest";
import { prisma } from "./generated/client";
bench("Customers: getInfo", () => {
prisma.customer.findFirst({
select: { id: true, companyName: true, contactName: true },
});
});
bench("Orders: getById", () => {
prisma.order.findUnique({
where: { id: 1 },
include: { customer: true, employee: true },
});
});
These are the equivalent snippets from the Drizzle benchmarks:
import { bench } from "@ark/attest";
import { db } from "./db";
import { customers, orders, employees } from "./schema";
bench("Customers: getInfo", () => {
db.query.customers.findFirst({
columns: { id: true, companyName: true, contactName: true },
});
});
bench("Orders: getById", () => {
db.query.orders.findFirst({
where: (orders, { eq }) => eq(orders.id, 1),
with: { customer: true, employee: true },
});
});
import { bench } from "@ark/attest";
import { db } from "./db";
import { customers, orders, employees, details } from "./schema";
bench("Customers: getInfo", async () => {
await db.select().from(customers).where(eq(customers.id, "id1"));
});
bench("Orders: getById", async () => {
await db
.select({
id: orders.id,
shippedDate: orders.shippedDate,
shipName: orders.shipName,
shipCity: orders.shipCity,
shipCountry: orders.shipCountry,
productsCount: sql<number>`count(${details.productId})`.as("productsCount"),
quantitySum: sql<number>`sum(${details.quantity})`.as("quantitySum"),
totalPrice: sql<number>`sum(${details.quantity} * ${details.unitPrice})`.as("totalPrice"),
})
.from(orders)
.leftJoin(details, eq(orders.id, details.orderId))
.where(eq(orders.id, "id5"))
.groupBy(orders.id)
.orderBy(asc(orders.id));
});
When the benchmarks are executed, the @ark/attest library type-checks each block and records:
m7i.large instance)This methodology ensures a proper comparison: same schema, same queries, same TypeScript version, measured through the same framework, leading to the same result metrics.
We report two sets of results measured on an m7i.large instance. The first one compares Prisma ORM with Drizzle 0.44.4 (stable at the time of testing) and remains the primary dataset. The second measures against Drizzle's recent beta release with improved type-checking performance.
Type instantiations:
| Prisma ORM | Drizzle ORM | Difference (count) | Difference (%) |
|---|---|---|---|
| 428 | 41150 | +40722 | +9514% |
Check time:
| Prisma ORM | Drizzle ORM | Difference (ms) | Difference (%) |
|---|---|---|---|
| 205.02ms | 601.58ms | +396.56ms | +193% |
Type instantiations:
| Label | Prisma ORM | Drizzle RQB API | Difference (%) |
|---|---|---|---|
| Customers: getInfo | 437 | 731 | +67% |
| Customers: search | 213 | 341 | +60% |
| Employees: getInfo | 1050 | 1710 | +63% |
| Suppliers: getInfo | 429 | 697 | +62% |
| Products: getInfo | 900 | 1626 | +81% |
| Products: search | 213 | 341 | +60% |
| Orders: getAll | 1538 | 1441 | -6% |
| Orders: getById | 1542 | 1649 | +7% |
| Orders: getInfo | 747 | 1950 | +161% |
| Average | 785 | 1165 | +48% |
| Label | Prisma ORM | Drizzle SQL Query Builder | Difference (%) |
|---|---|---|---|
| Customers: getInfo | 437 | 751 | +72% |
| Customers: search | 213 | 701 | +229% |
| Employees: getInfo | 1050 | 8448 | +705% |
| Suppliers: getInfo | 429 | 699 | +63% |
| Products: getInfo | 900 | 1753 | +95% |
| Products: search | 213 | 609 | +186% |
| Orders: getAll | 1538 | 2179 | +42% |
| Orders: getById | 1542 | 2385 | +55% |
| Orders: getInfo | 747 | 2671 | +258% |
| Average | 785 | 2244 | +186% |
Check time:
| Prisma ORM | Drizzle RQB API | Difference (%) |
|---|---|---|
| 335ms | 697ms | +108% |
| Prisma ORM | Drizzle SQL Query Builder | Difference (%) |
|---|---|---|
| 335ms | 774ms | +131% |
Drizzle recently published a beta with type-checking improvements. We re-ran the suite against this version to provide an up-to-date comparison; check times below are the re-measured results from the same machine as above.
Type instantiations:
| Prisma ORM | Drizzle ORM | Difference (count) | Difference (%) |
|---|---|---|---|
| 428 | 5017 | +4589 | +1072% |
Check time:
| Prisma ORM | Drizzle ORM | Difference (ms) | Difference (%) |
|---|---|---|---|
| 191.18ms | 369.19ms | +178.01ms | +93% |
Type instantiations:
| Label | Prisma ORM | Drizzle RQB API | Difference (%) |
|---|---|---|---|
| Customers: getInfo | 437 | 364 | -17% |
| Customers: search | 213 | 282 | +32% |
| Employees: getInfo | 1050 | 514 | -51% |
| Suppliers: getInfo | 429 | 364 | -15% |
| Products: getInfo | 900 | 514 | -43% |
| Products: search | 213 | 298 | +40% |
| Orders: getAll | 1538 | 955 | -38% |
| Orders: getById | 1542 | 1022 | -34% |
| Orders: getInfo | 747 | 459 | -39% |
| Average | 785 | 530 | -32% |
| Label | Prisma ORM | Drizzle SQL Query Builder | Difference (%) |
|---|---|---|---|
| Customers: getInfo | 437 | 883 | +102% |
| Customers: search | 213 | 838 | +293% |
| Employees: getInfo | 1050 | 2963 | +182% |
| Suppliers: getInfo | 429 | 851 | +98% |
| Products: getInfo | 900 | 1779 | +98% |
| Products: search | 213 | 821 | +285% |
| Orders: getAll | 1538 | 2451 | +59% |
| Orders: getById | 1542 | 2657 | +72% |
| Orders: getInfo | 747 | 2781 | +272% |
| Average | 785 | 1780 | +127% |
Check time:
| Prisma ORM | Drizzle RQB API | Difference (%) |
|---|---|---|
| 284ms | 416ms | +47% |
| Prisma ORM | Drizzle SQL Query Builder | Difference (%) |
|---|---|---|
| 284ms | 519ms | +83% |
For a fair comparison, this analysis looks at Prisma ORM vs. Drizzle's Relational Query Builder (RQB) API, since both provide higher-level relational abstractions. We're also analysing the results of the comparison with Drizzle's beta version since that version performs notable better than the current live version of Drizzle ORM.
The schema benchmarks show the biggest difference. Prisma's generated schema types require only a few hundred instantiations, while Drizzle's inferred schemas require more than five thousand. That's because Drizzle asks the compiler to "re-derive everything from first principles" in each build, while Prisma "front-loads" that work during its code generation step.
The result is that Prisma's check time is not just faster, but also more predictable as schemas grow. In practice, it means that as your app scales, Prisma's type-checking performance remains relatively stable and easy to anticipate, while Drizzle's may degrade in spiky or exponential ways as the schema grows.
For queries, Drizzle beta version leads to fewer type instantiations but still has a higher check time, suggesting that type instantiations are not the most important metric and actually measuring the time needed by the compiler remains crucial when evaluating actual type checking performance.
So what accounts for the difference? Several architectural advantages in Prisma lead to better type performance, let's go through them.
Prisma performs type-heavy work once during prisma generate. The output .d.ts files cache type relationships so the compiler doesn't have to redo them on every keystroke.
<Quotes speakerImgLink="/blog/why-prisma-orm-checks-types-faster-than-drizzle/imgs/e7f6b90fa12f4e616e865df20bc2b56f15b7a992-400x400.jpg" speakerName="David Blass"
Prisma has a big advantage — there's a buildtime step where lots of types are constructed.
</Quotes>Drizzle, by contrast, re-derives types each time a query is written. This explains why Prisma's instantiation counts are usually much lower.
Prisma's generated types avoid deep recursion, prefer interfaces for structural reuse, and minimize conditional unions — all of which align with TypeScript's internal caching model.
By generating types instead of inferring them, Prisma sidesteps common pitfalls like non-homomorphic mapped types, deeply nested conditional types, or intersection-heavy unions, patterns known to slow down the TypeScript compiler.
As part of our collaboration with David Blass, we also underwent a major optimization effort for our type checking performance. Here's a summary of a few things we did:
commonInputTypes) to reuse interfaces instead of duplicating shapes. Result: file size down 76% (339 → 82 LOC), check time down 45%.readonly arrays as the base case: Prevents inference traps where readonly tuples break assignability to any[].If you're curious about the details, you can check out the PRs (#27775 and #27777) where we implemented the optimizations.
Prisma ORM and Drizzle ORM both embrace TypeScript, but they take very different routes. Prisma ORM uses code generation to keep types fast and stable. Drizzle ORM leans on type inference for flexibility, but that flexibility can come with performance costs.
Our benchmarks showed the following:
Note that Drizzle's 1.0.0 beta significantly reduces schema instantiations to ~5k (+1072% vs Prisma ORM). For queries, Drizzle’s RQB now averages 32% fewer instantiations than Prisma, but check times remain ~1.5× slower.
You can explore all the benchmark files in our open-source repo.
If you are building a large, complex app where developer velocity and CI performance matter, Prisma ORM’s approach scales further and faster. Most importantly, type performance is developer experience. The faster your editor responds, the quicker your feedback loop and the happier your developers.
We used Drizzle ORM versions
0.44.4(stable) and1.0.0-beta.1-2acab7fand compared them against Prisma ORM6.15.0.