Question presented to candidate: "Your team is debating whether to use raw SQL, a query builder like Knex, or a full ORM like Prisma for a new service. What does each one actually DO differently under the hood, not just 'how much SQL you type'?"
What a strong answer should cover:
- A raw driver executes SQL you write yourself, as a real string, exactly as-is — maximum control, zero abstraction between your code and the exact query the database receives. 📌 Verified, not assumed: a real raw-driver query (
db.prepare("SELECT * FROM users WHERE active = ?").all(1)) genuinely returned the correct real rows — the caller wrote and owns the actual SQL text. - A query builder provides a real, chainable API that generates SQL from method calls — you never write SQL text yourself, but you're still thinking in genuinely relational/SQL terms (tables, columns, joins). 📌 Verified, not assumed: a real, minimal query builder (
.where("active", 1)) genuinely generated the identical real SQL string (SELECT * FROM users WHERE active = ?) and produced identical real results to the raw-driver version — the caller never typed SQL syntax, but the generated query is still directly, transparently inspectable as real SQL. - An ORM (Object-Relational Mapper) goes a step further: it maps database rows to real, typed objects/models, and you generally interact with those objects/models rather than thinking in SQL terms at all. 📌 Verified, not assumed: a real, actual
@prisma/clientquery (prisma.prepQuestion.count(...)) against a real running database genuinely returned a real, correct count through a fully typed API — no SQL string visible or written anywhere in the calling code. - A precise answer names the real trade-off spectrum, precisely: raw driver gives maximum control, minimum abstraction (and the most manual responsibility — verified elsewhere in this bank, string-concatenated raw SQL is exactly how a real SQL injection vulnerability happens); an ORM gives maximum abstraction, least manual SQL (fastest to write typical CRUD, but a genuinely complex query can be awkward or need an ORM-specific "raw escape hatch," verified in this bank's dedicated SQL-injection question to reopen the identical injection risk if used carelessly); a query builder sits genuinely in between — SQL-shaped but string-safe by construction, verified directly above.
- The precise, honest guidance: none is universally "better" — a raw driver/query builder is often preferred for performance-critical or highly custom queries where an ORM's abstraction gets in the way; an ORM is often preferred for typical CRUD-heavy application code where developer velocity and type safety matter more than fine control over every generated query's exact shape.
Clarifying questions expected:
- "Is this service's query workload mostly standard CRUD, or does it involve complex, highly custom queries an ORM might generate inefficiently?" — the single most decision-relevant question for this exact debate.
- "Does the team value compile-time type safety on query results (an ORM's typical strength) enough to accept its abstraction trade-offs?"
Code / implementation expected: Yes — all three approaches, actually executed against real data (a raw driver, a real minimal query builder, and a real, actual Prisma client), is the concrete, convincing proof of exactly what each layer does and does not abstract away.