RealtimeKeep your app up to date
AuthenticationOver 80+ OAuth integrations
Convex Components
ComponentsIndependent, modular, TypeScript building blocks for your backend.
Open sourceSelf host and develop locally
AI CodingGenerate high quality Convex code with AI
Compare
Convex vs. Firebase
Convex vs. Supabase
Convex vs. SQL
DocumentationGet started with your favorite frameworks
SearchSearch across Docs, Stack, and Discord
TemplatesUse a recipe to get started quickly
Convex ChampionsAmbassadors that support our thriving community
Convex for StartupsStart and scale your company with Convex
Convex for Open SourceSupport for open source projects
Convex CommunityShare ideas and ask for help in our community Discord
Stack
Stack

Stack is the Convex developer portal and blog, sharing bright ideas and techniques for building with Convex.

Explore Stack
BlogChangelogDocsPricing
npm1.3M weekly
GitHub20,940 stars
Log inStart building
Agent evals

The backend platform your agents can’t break

Agents should build what you asked for, correctly. You can catch frontend bugs in a browser, but the real failures, the ones that surface at load on launch day, are much harder to find. In our research, frontier models often write build systems that fail at scale, all the while claiming they pass code review with flying colors.

To test this, we asked several agents (Opus 4.8 and Fable from Anthropic, as well as GPT-5.6-Sol and GPT-5.6-Terra from OpenAI) to one-shot the same ten apps on Postgres, then again on Convex.

9 out of 10 apps silently corrupted data on Postgres. None of them did on Convex.

0

Zero writes lost on Convex

Database updates executed correctly, over time

More users, more problems

Legacy relational databases are full of subtle traps at scale. One of the apps the agent wrote managed shopping carts, but on Postgres the more shoppers buy at once, the more updates are silently dropped. The same app built on Convex stays correct as users scale.

Concurrent shoppers
1
0%25%50%75%100%141664256Concurrent shoppers over timeConvex100% correctPostgres28% correct
0% of writes lost
The apps, the load tests, and the measured data →
Chart of correct database updates as concurrent shoppers grow from 1 to 256. Convex holds at 100% correct. Postgres falls to 28% correct.

A verbatim example prompt

"Hey, I run a small online shop and need a backend, please.

Catalog. Each item has a name, a price, and how many are in stock.

Buy. A customer buys an item. After a purchase, the item's remaining stock goes down by one, and we keep a record of the sale.

... Build the whole backend: schema, seed a few items (some with only 1 or 2 in stock), and an API. However you think is best."

Nothing about locks, transactions, or races. Just “let people buy things”.

Don’t leave correctness to chance

On Convex, correctness is the default, not a //TODO

The real test

Correct by default

Under load, most of the Postgres apps fell over or silently corrupted data, all while answering a cheerful HTTP 200. The Convex apps had none of these issues.

Why the split? On Convex the exact same code is correct by default: every mutation runs as one serializable transaction and retries on conflict, so the first thing an agent reaches for just works, no matter how many requests hit at once. On Postgres it's the opposite default: quietly wrong under load, unless someone knows to add the right lock or constraint to every single operation by hand.

Comparison of Postgres and Convex app correctness under concurrent load. Expand a failing Postgres result for details.
App under
concurrent load
Postgres,
as generated
Convex,
correct by default

Bill splitting

2 Edits at once

Edit lost

data lost

Correct by default

data lost

Postgres

Two guests edit the same split at once; last-write-wins drops one change and the bill no longer adds up. Data lost.

Correct

Convex

Concurrent edits conflict on the same document and retry against the latest state, so no edit is silently dropped and the split always balances. Correct by default.

Booking

Overlapping times • 2 requests

2 Booked

double-booked

Correct by default

double-booked

Postgres

Two appointments whose times overlap both clear the availability check before either commits — the clash guard locks nothing when it finds no conflict — so one stylist is booked for two customers at once. Double-booked.

Correct

Convex

Each booking runs as one serializable transaction that reads the overlapping time range; a competing appointment that overlaps conflicts on that read and retries, so a stylist can't be booked twice. Correct by default.

Kanban

10 Moves • One slot

8 Collide

order corrupted

Correct by default

order corrupted

Postgres

Concurrent card moves reuse the same position index, so several cards land on one slot and the board order silently corrupts. Order corrupted.

Correct

Convex

Reordering happens inside a serializable mutation; concurrent moves that touch the same position conflict and retry, so no two cards ever claim one slot. Correct by default.

Ledger

10 Entry • 2 Reversals

Reversed 8x

corrupted records

Correct by default

corrupted records

Postgres

Reversal handlers race on the same entries, applying the reverse multiple times and leaving the ledger balances wrong. Corrupted records.

Correct

Convex

A reversal is a serializable mutation keyed to its entry; a second reversal of the same entry conflicts and can't apply twice, so balances stay correct. Correct by default.

Marketplace

Add item during checkout

Item lost

unpaid & gone

Correct by default

unpaid & gone

Postgres

A cart update mid-checkout drops an item from the paid total while inventory still decrements, so the buyer loses the item unpaid. Unpaid & gone.

Correct

Convex

Cart edits and checkout mutate the same records transactionally; a mid-checkout change conflicts with the checkout and retries, keeping the paid total and inventory in sync. Correct by default.

Q&A forum

10 Votes at once

Score 3

miscounted

Correct by default

miscounted

Postgres

Ten simultaneous votes each read the same score and write score+1, so most increments are lost and the tally undercounts. Miscounted.

Correct

Convex

The vote reads and writes the score in a single transaction; concurrent votes conflict on the document and retry, so every increment lands and the tally is exact. Correct by default.

Seat ticketing

1-seat waitlist • 2 freed at once

2 to 1 buyer

over-allocated

Correct by default

over-allocated

Postgres

Two seats freed at the same moment are both offered to the same one-seat waitlister, who buys both — while the next person in line gets none. The offer step reads the waitlist without locking it. Over-allocated.

Correct

Convex

Offering a freed seat runs in one serializable transaction that reads the next waiter's row; two seats freeing at once conflict on that read and retry, so each waiter is offered exactly one seat and no one is skipped. Correct by default.

Shop

Sold-out • Many buyers

Server hangs

Deadlocked

Correct by default

Deadlocked

Postgres

The failure branch grabs a 2nd pooled connection while still holding the 1st. With a 10-connection pool, 10 concurrent buys on a sold-out item each hold one and wait forever for another. The whole server deadlocks; /health keeps lying {ok:true}. Deadlocked.

Correct

Convex

Convex has no connection pool to exhaust, and mutations don't hold connections across awaits, so a flood of buys on a sold-out item can't deadlock the server. Correct by default.

Warehouse

5 in stock • Many picks at once

-4 on hand

oversold

Correct by default

oversold

Postgres

Concurrent picks race past the stock check and each decrement inventory, driving on-hand count negative while every response still returns success. Oversold.

Correct

Convex

Each pick runs as a serializable transaction, so two picks that read the same stock can't both commit — the loser automatically retries against the updated count. There is no lock to place or forget. Correct by default.

Card game

Concurrent turns

Correct

Correct by default

The Score

9 of 10 silently broken

Looks right, acts wrong

100% consistent and correct

Does what it is supposed to

See the exact code, app by app

More features, fewer tokens

Realtime out of the box, not hand-wired

The reactivity tax

Realtime, out of the box

Screens don't update themselves. On Postgres, realtime is plumbing the agent has to build by hand (SSE or WebSockets re-pushed on every write), exactly the kind of thing that gets skipped or half-shipped. On Convex there's nothing to wire: every query is a live subscription, so any committed write reaches every watching client on its own.

Alice and Bob watch the same live feed. On Convex both screens update together as new messages arrive. On the generated Postgres app, Alice's own tab reflects her writes but Bob's screen freezes and shows stale data.
Convex

both screens stay live

A

Alice

Live

Bob:Sale is live 🚀

Alice:3 orders already

Bob:Make that 9

Alice:Down to 2 left

Bob:Sold out 🎉

B

Bob

Live

Bob:Sale is live 🚀

Alice:3 orders already

Bob:Make that 9

Alice:Down to 2 left

Bob:Sold out 🎉

Postgres

no realtime updates

A

Alice

In sync

Bob:Sale is live 🚀

Alice:3 orders already

Bob:Make that 9

Alice:Down to 2 left

Bob:Sold out 🎉

B

Bob

Stale · 0s

Bob:Sale is live 🚀

Alice:3 orders already

Bob:Make that 9

Alice:Down to 2 left

Bob:Sold out 🎉

The same write, two screens. Convex pushes every committed change to all watching clients instantly, no extra code. A generated Postgres app has no live channel, so the second screen just shows stale data, without updating or warning, until someone thinks to reload. Across the ten apps: Convex was live on all of them, Postgres on essentially none.

Illustration of live updates by app across ten builds. Convex delivered complete live updates on all ten. Postgres delivered none complete and two partial implementations with silent stale screens.
Convex

10 Complete

Postgres

0 Complete • 2 partial


Each dot is one of the ten apps. Filled = complete live updates by default. Convex delivers live updates from the query layer. Postgres apps hand-build SSE/WebSockets: eight built none, two shipped incomplete (silent stale screens), none complete.

Even agents make mistakes

9 of 10 Postgres apps shipped a bug it never reported

Not one model, not one run

Frontier models make the same kinds of mistakes

Complex, lower level abstractions (like Postgres) can trip up even the latest models.

GPT-5.6-Solkanban, on Postgres

Locked the column, in 5 of its 6 write paths. Missed card delete.

// POST /cards — create
SELECT ... FROM columns ... FOR UPDATE // locks the column
position = count(cards in column)
 
// DELETE /cards/:id — delete
DELETE FROM cards WHERE id = $1 // no column lock
renumber the survivors 0..n-1

A concurrent create and delete corrupt the (column, position) order. The column is bricked— every new card fails from then on.

Claude Fablemarketplace, on Postgres

Locked the listing and its stock. Missed the cart.

// checkout()
SELECT ... FROM cart_items ci JOIN listings l ...
FOR UPDATE OF l // locks listings, not the cart
// ... decrement stock, write the order ...
 
DELETE FROM cart_items WHERE user_id = $1 // deletes the whole cart

An item added to the cart mid-checkout is swept away by that blanket delete. Never ordered, never charged, gone.

The code, the briefs, and a written breakdown of each →
Why

We made it impossible to write incorrect transactions

Every Convex mutation runs as one serializable transaction: when two writes collide, the loser automatically retries against the committed result. That isn't a best practice your agent has to remember, it's the only way a mutation runs.

Postgres · code looks right

Postgres code looks right:

function sellItem(id) {
  const item = db.get(id);
  db.set(id, { stock: item.stock - 1 });
}

Two buyers read the same item and both write it back. One sale's decrement is silently lost. Oversold.

Convex · the same code, in a mutation

Convex the same code, in a mutation:

const sellItem = mutation((ctx, id) => {
  const item = ctx.db.get(id);
  ctx.db.patch(id, { stock: item.stock - 1 });
});

The identical read-and-write, wrapped in a mutation. Convex runs them one at a time and retries the loser against the new stock. Never oversold.

Want to dig into the methodology?

Models under test: Claude Opus 4.8, Claude Fable, GPT-5.6-Sol, and GPT-5.6-Terra

Review our open-sourced work
LLM Leaderboard

Curious how the models stack up?

Compare the performance of different language models on our LLM Leaderboard.

View the leaderboard

Build a production app this afternoon

Scale it for decades.

Claude Code
/plugin install convex
Codex
/plugins install convex
Works great with any model, any agent, any editor
npm create convex@latest
Read the docsStart building
Convex logo
ProductSyncRealtimeAuthOpen sourceAI codingFAQMerchEnterprisePricing
DevelopersDocsBlogComponentsTemplatesConvex for StartupsConvex for Open SourceChampionsPodcastsLLMs.txt
CompanyAbout usBrandInvestorsBecome a partnerJobsNewsEventsSecurityLegal
SocialX (Twitter)DiscordYouTubeLumaLinkedInGitHub
A Trusted Solution
  • SOC 2 Type II Compliant
  • HIPAA Compliant
  • GDPR Verified
©2026 Convex, Inc.