skip to content

Context & Connection Lifetime

How long a unit of work and the connection under it stay open: per transaction, per request, across a conversation, or longer, and what each leaks. Asked because starved pools trace back to it.

on this pageshow

questions

5

When a data-access layer opens and closes a unit of work, what happens to the underlying database connection?

level: juniorimportance: must knowfreq 66%

answer

  1. a scope, not just an object
  2. borrow, use, hand back
  3. commit and close are different acts
  4. unclosed units drain the pool
  5. waiting callers, idle server

basics

~20 s

Opening a unit of work borrows a pooled connection; every statement it runs travels over that connection; closing hands it back, still established. A unit that is never closed keeps holding it, and the pool drains.

solid answer

~50 s

A unit of work is a scope, not just an object: while it is open it owns a database connection borrowed from the pool, and everything it reads or writes goes over that connection. The pool keeps a small number of already-established connections, so opening a unit of work usually does not create a new network connection to the server; it takes one that exists. Closing the unit ends its transaction (committing or rolling back what is pending), stops tracking the objects it loaded, and returns the connection to the pool rather than tearing it down. The bug that follows from getting this wrong is a unit of work left open on some path: the connection never comes back, the pool loses one slot per leak, and callers eventually block waiting to borrow while the database server itself looks idle.

go deeper

for a junior

Remember the three verbs: borrow, use, hand back. Opening a unit of work takes a connection out of a shared pool, and closing it puts that connection back for someone else.

for a middle

Be able to separate the events — commit ends the transaction, closing ends the scope and returns the connection — and to say why a leaked unit of work costs a pool slot permanently.

for a senior

Name the diagnostic signature out loud: callers blocked waiting to borrow while the server is idle means the connections are held by application code doing non-database work or never closed at all.

for a principal

Frame it as a shared-resource budget. Connection slots are a service-wide allocation, so the reviewable rule is how long any code path may hold one, enforced structurally rather than by developer discipline.

## What a unit of work is A **unit of work** is the scope a data-access layer keeps between the moment it starts talking to the database and the moment it is finished. Three things live inside that scope: - the **tracked set** of objects it has loaded, so the same row read twice yields the same object; - the **pending changes** it has noticed but not yet turned into statements; - the **connection** those statements travel over, and the transaction running on it. The third item is the one that costs a shared resource, and it is the one juniors are asked about first. Objects and pending changes cost memory in one process. A connection is one of a small, fixed number of slots shared by the whole service. ## Where the connection comes from Applications almost always borrow from a **connection pool** rather than dialling the server per request: establishing a connection means a network round trip, authentication and server-side setup, which is far more expensive than the query that follows. So: 1. Code opens a unit of work at some boundary — the start of a request handler, a job step, a service method. 2. The layer takes a connection from the pool, either right then or lazily at the first statement it actually needs to send. 3. Every statement of that unit's transaction travels over that same connection, because a transaction is server-side state bound to one connection. 4. When the scope ends, the connection goes back to the pool, still open, ready for the next caller. ## What closing actually releases | Event | Ends the transaction | Frees the tracked objects | Returns the connection | |---|---|---|---| | Commit | Yes | Not by itself | Only when the scope that borrowed it also ends | | Rollback | Yes | Not by itself | Only when the scope that borrowed it also ends | | Closing the unit of work | Yes, if one is still open | Yes — they become detached | Yes | | Process exit or crash | Server-side, eventually | Irrelevant | The server reclaims after a timeout | The row that surprises people is the first one. **Commit and close are not the same act.** Committing says "make these changes durable and end the transaction"; closing says "I am finished with this scope, take the resources back." Layers differ in how tightly the two are coupled — some release the connection as soon as the transaction ends, others keep it until the scope is disposed — so the safe habit is to close explicitly and not to reason from what one layer happens to do. ## Why a leaked unit of work is the expensive mistake A connection that is never handed back is gone for the life of the process. The damage is cumulative and quiet: - Each leaked path removes one slot. With a pool of a few dozen slots, a leak on an error branch that fires occasionally will starve the service hours or days after deployment. - The **signature is unmistakable once you know it**: callers time out *waiting to borrow* a connection while the database server shows low CPU, low I/O and mostly idle sessions. The queue is in the application, not in the engine. - Reads leak exactly as writes do. A unit of work that only ran a SELECT still borrowed a connection, and "it did not change anything" is no reason to skip closing it. - Relying on garbage collection is not a strategy: the object may be collected late or never, and until then the pool slot is out of circulation. The defence is structural rather than diligent. Let the framework or an enclosing scope own the boundary so that the close happens on the success path and the failure path alike, rather than writing a close at the end of a method that an early return or a thrown error can skip. ## Practical rules - Open the unit of work as late as you can and close it as early as you can; the interesting number is not how many statements it ran but how long it was open. - Never leave a unit of work open across something that is not database work — a call to another service, waiting on a lock, rendering a response, or a human deciding what to click. - Treat "how long is a unit of work open" as a reviewable property of a code path, the same way you treat what it queries. - If a pool starves, look first for the paths that hold a connection while doing something else, and only then at how big the pool is.

  • If code throws before it reaches the close, what stops the connection from being lost?
    Nothing automatic inside the method — which is why the close belongs to an enclosing scope that runs on both the success and the failure path, not to a line at the end of the body. Framework-managed boundaries and block-scoped ownership constructs exist for exactly this. Pools can also reclaim a connection borrowed far too long, but that is a backstop that hides the bug rather than a design.
  • Does a unit of work that only reads still have to be closed?
    Yes. It borrowed a connection like any other, and on many layers it also opened a read transaction that the server keeps alive until it ends. The absence of writes changes what has to be committed, not whether the resources have to be handed back.
  • Why is borrowing from a pool preferable to connecting per statement?
    Establishing a connection costs a network round trip, authentication and server-side setup, often more than the statement itself; and the server can only host so many connections at once. A pool pays that cost a fixed number of times at startup and lends the results out, so the per-call cost becomes a hand-off rather than a handshake.

saying these in an interview costs you the question

  • Thinks each statement opens a fresh connection to the database server
  • Believes closing a unit of work tears down the network connection
  • Assumes a read-only unit of work costs nothing and need not be closed
  • Relies on garbage collection to release units of work and connections
  • Treats commit as automatically releasing every resource the scope holds
open as a page

How do per-transaction, per-request and conversation-spanning unit-of-work scopes differ, and what does each one cost?

level: middleimportance: must knowfreq 58%

basics

~20 s

The three scopes differ in how long the tracked set and its connection stay alive - one transaction, one request, or a flow spanning requests. Longer scopes reuse more objects, go stale more often, and hold a shared connection longer.

open as a page

Why does it matter whether a data-access layer takes its connection at the boundary or at the first statement?

level: middleimportance: should knowfreq 44%

basics

~20 s

Acquisition timing decides how much of a boundary's duration occupies a pool slot. Taking the connection at the boundary is predictable but pays for pre-work; taking it at the first statement lets a boundary that never queries borrow nothing.

open as a page

What does keeping the unit of work open through response rendering buy a team, and what does it cost?

level: seniorimportance: should knowfreq 50%

basics

~20 s

Convenience: deferred links still resolve while the view walks the object graph, so nobody declares in advance what a screen needs. It costs a connection held through the slowest phase and statements issued from a layer nobody reviews.

open as a page

How would you set unit-of-work and connection-lifetime policy for a service mixing short requests, long batch jobs and multi-step flows?

level: principalimportance: should knowfreq 38%

basics

~20 s

Set policy per workload against one invariant: a unit of work is open only while it is issuing statements. Requests get a thin boundary; batches one unit per chunk with the tracked set discarded between chunks; flows keep detached state.

open as a page