Databaseshigh

Async Query Patterns

Database queries in Node.js are asynchronous—they return Promises (or accept callbacks in older drivers). Use async/await for sequential queries and Promise.all for parallel independent queries.

Memory anchor

Sequential awaits = ordering food, waiting for it to arrive, THEN ordering drinks. Promise.all = ordering food AND drinks at the same time—both arrive in parallel. Transactions = one waiter (connection) who must carry ALL your dishes on one tray (BEGIN...COMMIT) or drop them all (ROLLBACK).

Expected depth

Sequential (dependent) queries use await: `const user = await getUser(id); const orders = await getOrders(user.id)`. Parallel (independent) queries use Promise.all: `const [user, products] = await Promise.all([getUser(id), getProducts()])`. Promise.allSettled() runs all queries regardless of failures, returning status+value/reason for each. Transactions require a single connection held across multiple queries: acquire a client from the pool, BEGIN, run queries, COMMIT or ROLLBACK on error, release the client.

Deep — senior internals

Transaction isolation in Node.js requires careful connection management. Using pool.query() for transactions is wrong—pool.query() may use different connections for each call. Correct pattern: `const client = await pool.connect(); await client.query('BEGIN'); try { await client.query(q1); await client.query(q2); await client.query('COMMIT'); } catch (e) { await client.query('ROLLBACK'); throw e; } finally { client.release(); }`. Async iterators for large result sets: pg's Cursor and many ORMs support streaming query results, enabling per-row processing without buffering the entire result set in memory. For high-throughput bulk inserts, use multi-value INSERT statements or COPY (PostgreSQL) rather than individual INSERT per row.

🎤Interview-ready answer

Use async/await for sequential queries, Promise.all for parallel independent queries. Database transactions require a single borrowed pool connection across all query calls—using pool.query() for transactions is a bug because different calls may use different connections. Always release connections in a finally block. Stream large result sets with database cursors to avoid buffering entire tables in memory.

Common trap

await query1; await query2 when the queries are independent serializes them unnecessarily. Two 50ms queries run sequentially take 100ms; with Promise.all they take 50ms. In a high-traffic API this serialization compounds across thousands of requests. Profile query execution to identify sequential waits on independent operations and parallelize them.

Related concepts