On this page
SQLite vs Postgres: Which Should You Use?
SQLite is fast and zero-ops but single-writer. Postgres is concurrent and multi-user. Compare them, and see why local SQLite plus prod Postgres breaks.
Quick answer
- SQLite is an in-process library, not a server. Postgres is a server you connect to. That single difference explains almost every practical trade-off.
- SQLite is often faster for ordinary app workloads because there is no network hop per query.
- The hard limits are one writer at a time, one machine, and no network protocol — not raw speed.
- Developing on SQLite and deploying to Postgres is a common source of production bugs, because the two disagree about types and strictness.
How are SQLite and Postgres different?
SQLite and PostgreSQL are both relational databases that speak SQL, but they are not two implementations of the same deployment model. SQLite is a library that your application links against, storing data in a file on local disk. PostgreSQL is a server process — possibly on another machine — that clients connect to over a network.
That architectural difference cascades into everything else. SQLite has no connection to configure, no credentials, no server to patch, and no network latency, which is why it can be the fastest option for a single-process app. It also means one process owns the file, writes are serialised, and there is no built-in way for a second machine to reach the data.
Postgres pays a network round trip and an authentication step on every connection in exchange for concurrent access, multiple clients, roles and permissions, replication, and a query planner built for large datasets.
Where this bites vibecoders
The specific way this bites vibecoders is the invisible engine swap. An AI assistant scaffolds a project, picks SQLite because it requires no setup, writes the whole application against it, and never records the choice. Weeks later you deploy and switch the connection string to Postgres — and the app behaves differently, because the two databases disagree about type coercion, case-insensitive matching, and how strict they are about what a column accepts. Nothing in the code says “this only works on SQLite.”
What are the real differences?
| Dimension | SQLite | PostgreSQL |
|---|---|---|
| Architecture | Embedded library, file on disk | Client/server, network connection |
| Setup | None — point at a file path | Install, configure, secure, back up |
| Writers | One at a time | Many concurrently |
| Readers | Many concurrently (WAL mode) | Many concurrently |
| Multiple machines | Not possible | Native, via replication |
| Network access | None — file must be reachable | Core design |
| Typical read latency | Lower (no network hop) | Higher (round trip) |
| Type strictness | Type affinity, permissive | Strict, rejects bad input |
| Rich types | Limited | Arrays, JSONB, ranges, enums, UUID |
| Extensions | Very limited | Large ecosystem including PostGIS, pgvector |
| Row-level security | No | Yes |
| Backup model | Copy the file (safest: .backup API) | Logical dump or physical base backup |
| Best at | Single-process apps, local dev, embedded, tests | Multi-user apps, concurrent writes, production |
The honest summary: SQLite wins on operations, Postgres wins on concurrency and data model. They are not competing for the same job, which is why “which is better” is usually the wrong question.
When should you use SQLite?
SQLite is the right choice when the app is a single process, writes are infrequent relative to reads, and you want to avoid operating a database at all.
Concretely, it fits: local development and CLI tools, desktop and embedded applications, serverless functions where the filesystem is ephemeral or durable-per-instance, read-heavy content sites, and test suites where spinning up a server per run is friction. A single-user or small-team internal tool is a classic fit.
The rule of thumb that works: if only one process ever writes to the database, SQLite is usually the better engineering choice — fewer moving parts, no credentials to leak, and backups that are a file copy.
When should you use Postgres?
Reach for Postgres when the architecture requires more than one writer, more than one machine, or richer data modelling.
That means: a multi-user application where many people save at once, anything deployed across multiple instances behind a load balancer, data that needs rows to be read and written in strict transaction order, and any use of extensions such as PostGIS or pgvector. Postgres is also the right answer when you need real roles and row-level permissions rather than a single set of credentials.
Migration is not a cliff, but it is work — dialect differences, type mappings, and the concurrency assumptions baked into your queries. Planning for it early is cheaper than discovering it during a launch.
Why does local SQLite plus production Postgres cause bugs?
Because the two engines resolve the same ambiguous input differently, and your code will be written against whichever one you tested on.
Type affinity versus strictness. SQLite applies affinity: it tries to convert a value to the column’s type rather than rejecting it. Postgres raises an error. A boolean sent as the string "true", a number arriving as "42", or a date in a non-ISO format may work locally and fail on deploy.
Case-insensitive matching. LIKE in SQLite is case-insensitive for ASCII by default. In Postgres, LIKE is case-sensitive and you need ILIKE for the other behaviour. Search endpoints behave differently in front of real users.
Returning inserted rows. SQLite supports RETURNING in recent versions, but the idiomatic “get the ID I just inserted” differs, and code written with one engine’s idiom may not compile on the other.
Concurrency. SQLite serialises writers. Code that appears correct locally can deadlock or hit database is locked under real concurrency, which is a bug that only appears after launch.
Constraints and defaults. NOT NULL, UNIQUE, CHECK, and default-value behaviour are close but not identical, and autogenerated schemas frequently differ.
Where AI coding assistants get this wrong
- Silently choosing SQLite for a multi-user application, because the scaffold “just works” and nobody revisited it.
- Writing
ILIKE,ON CONFLICT ... RETURNING, orSERIALwithout knowing which engine you run — all Postgres-isms. - Assuming
LIKEbehaves the same everywhere, then shipping a search box that works in dev and misses results in production. - Storing booleans as integers or strings and relying on SQLite’s coercion to hide it.
- Not recording the engine choice anywhere, so the next contributor — human or AI — has no idea the codebase assumes one.
- Testing only against whatever the development machine happens to have installed, which is the most common cause of deploy-day failures.
- Opening a long-lived connection and assuming it is fine, which works for a file and fails against a server with connection limits — see How to Fix ‘Too Many Connections’ in Postgres.
Checklist
- Use SQLite when exactly one process writes to the database.
- Move to Postgres when you need concurrent writers, multiple app instances, or real roles.
- Do not choose on raw speed; measure your own query shapes on the engine you will run.
- Record the chosen engine in the repo so it is visible to the next contributor.
- Keep the connection string in configuration, not in code, so the engine can change without a rewrite.
- Test against the production engine, not only the local one.
- Be explicit about boolean, date, and null handling instead of relying on coercion.
- Remember
LIKEis case-sensitive in Postgres; useILIKEor normalise explicitly. - Enable WAL mode if you use SQLite with concurrent readers.
- Back up a SQLite database with the
.backupAPI rather than copying the file while it is open.
FAQ
Is SQLite fast enough for production?
For many read-heavy applications, yes — and it is frequently faster than a networked Postgres, because there is no network round trip per query. The limits are concurrency and topology: SQLite allows one writer at a time, cannot run across multiple machines, and has no network protocol. Those are hard architectural limits, not tuning problems.
Why does my app break when I deploy with Postgres after developing on SQLite?
Because the two do not agree on the details your app depends on. Type affinity and strictness differ, so a value SQLite silently coerces may be rejected by Postgres. Concurrency behaves differently, boolean handling differs, and some features like case-insensitive matching and returning inserted rows are not portable. Test against the real engine before deploying.
Can SQLite handle multiple users?
Yes, for reads, and this surprises people — SQLite supports many concurrent readers. What it does not support is multiple writers: writes are serialised, and in the default rollback-journal mode a writer blocks readers too. An application where every user saves something is where Postgres becomes necessary.
Do I need a separate database server for a small app?
Often not. If your app is a single process, SQLite removes an entire service to deploy, secure, monitor, back up, and pay for. The moment you run two instances of your app, or need more than one process writing, you need a server database — and that is a normal, expected transition rather than a failure of planning.
Is SQLite harder to back up?
Not meaningfully. The safest method is SQLite’s own .backup API, which produces a consistent copy while the database is open. Copying the file directly while writes are in flight can capture a torn state. The bigger difference is that Postgres backups are a scheduled operational concern with tooling and retention, whereas a SQLite backup is often just a file.
Should I learn Postgres or SQLite first?
SQLite, if you want to run something today with zero setup — it removes every install step between you and a working database. Postgres, if your goal is to work professionally, because its type strictness, tooling, and concurrency model are what most production systems use. They share enough SQL that the second one is quick to pick up.
Related topics
- How to Choose a Database for Your App: A Decision Guide
- Postgres vs MySQL: Which Should You Use?
- What Are the Different Types of Databases (and What Is Each One For)?
- Databases for AI Builders: Why Your AI-Generated App Gets Slow and Loses Data
- How to Fix ‘Too Many Connections’ in Postgres