Dispose and garbage collection
“What is the difference between Dispose and garbage collection?”
The garbage collector frees memory later. Dispose gives back connections and files now.
See it
In plain words
The garbage collector (GC) cleans up memory, but only when it decides to; you can't predict when. Some objects hold something scarcer than memory: a database connection, an open file. SqlClient keeps a pool: a few open connections it lends out and takes back. hands yours back now. using calls for you at the end of the block, even if an error is thrown. Forget it, and under load the app runs out of connections while memory looks fine.
- The garbage collector frees managed memory (memory the .NET runtime tracks) when it decides to: it is non-deterministic and knows nothing about scarce outside resources such as database connections, file handles or sockets.
- Types that own those implement , an interface with one method, ; releases the resource deterministically, and
using/await usingcompiles to try/finally, so it runs even when an exception is thrown. - For , Dispose returns the physical connection to the pool (default Max Pool Size 100).
- Forget it and the connection stays checked out until the GC happens to let the pool reclaim it; under load, new opens time out with .
- In ASP.NET Core the disposes the services it created when the ends.
In the lending API
The same repository before and after the fix. It is slice 3 code (the database), shown early because this is the classic leak. Press Next line to walk through it.
Lending.sln · where these 2 files sit
- src/Infrastructure/Servicing/
What they ask next
Interviewers check that you can explain every line. Tap a question to open it.
Explain this lineWhen exactly does
using vardispose?At the end of the block it is declared in, here the method's closing brace. Several
using varlines are disposed in reverse order. The compiler writes it as try { … } finally { conn?.Dispose(); }, so it runs even when an exception is thrown.What about the older
using (…) { }form?Same thing, but you choose the block with braces. Use it when the object should be disposed before the method ends, for example to give a connection back before a slow calculation.
Why not…Why not call
conn.Close()at the end?If any line before it throws, never runs and the connection leaks.
usingis a try/finally, so it always runs. For , calls , and both give the connection back to the pool.Explain this lineWhy
await usingand not plainusing?In async code, use
await usingon types that implement , the async version with , so cleanup that talks to the network or disk does not block a thread. For the gain is small. For a stream that still has to flush data to a file or a socket, it matters.Doesn't the garbage collector clean this up?
It frees memory when it decides to, usually after enough new objects are made, not when connections run out. It never calls . In the test, a forced let the pool take the leaked connections back, because the pool notices their owner object is gone. In production you cannot wait for that, and calling yourself is a code smell.
What is a finalizer?
A
~ClassName()method the GC runs before it frees an object. It runs late, on a separate thread, in no fixed order, and it makes collection slower. Only a class that directly owns an operating-system handle needs one, and , a built-in wrapper that closes an OS handle, usually does that job for you.
What is the Dispose pattern,
Dispose(bool)?For classes that own an operating-system handle directly, or that others will inherit from. Public calls
Dispose(true)and ; the finalizer callsDispose(false). If your class only holds other disposables, like a connection, make it and write a plain that disposes them.What if is called twice?
It must be safe. Keep a
disposedflag and return early the second time. Other methods called after Dispose should throw .Who disposes the objects the DI container builds?
The does. Scoped and transient objects that implement are disposed when their ends, which in ASP.NET Core is the end of the request. Singletons are disposed when the app shuts down. Never dispose an object that was injected into you: you don't own it. Objects you build yourself and register with
AddSingleton(instance)are not disposed by the container.How can that cause a leak?
A transient disposable taken from the root provider, the app-wide scope, is kept until the app stops, so every one ever created stays in memory. More on the page.
How does EF Core handle connections?
A opens a connection for each query or save and closes it right after, unless a transaction is open; the request disposes the DbContext. Leaks like this one come from ADO.NET (the low-level / API, as here) or Dapper (a thin library on top).
How would you spot pool exhaustion in production?
Errors that say max pool size was reached; the app slow under load while SQL Server's CPU is low; the session count for the app stuck at the pool limit in
sys.dm_exec_sessions. The SqlClient counternumber-of-reclaimed-connections, read with , counts connections the GC had to reclaim because nobody disposed them. Above 0 means a leak.Why not just raise Max Pool Size?
The leak fills a bigger pool too, only a little later, and each extra connection costs memory on SQL Server. Find the code path that doesn't dispose.
Should I dispose an after each call?
No. That is the classic exception: a new per call can run out of sockets under load. One shared instance has the opposite problem: it never sees DNS changes. Use , or one shared instance with set. Disposable does not always mean dispose after each use.
Does Dispose free the object's memory?
No. It releases the outside thing, like the connection. The object stays in memory until no reaches it and the GC collects it.
Traps
- Don't say
The garbage collector calls Dispose for me.SayThe GC never calls . A finalizer, if the class has one, runs late and only cleans up operating-system handles. - Don't say
Dispose frees the object's memory.SayDispose releases the connection or file now. The GC frees the memory later. - Don't say
Pool timeouts? Raise Max Pool Size.SayFind the connection that is never disposed. A bigger pool only delays the timeout.
The interview rubric of Karat, an interview-outsourcing company, flags accepting AI output without checking it. A missing using is easy to miss in generated code, because it works fine on your machine with one user. Read every generated of a connection, stream or file and ask: who disposes this? A worked answer from this app's research: an AI suggested making a singleton to fix an ; the real fix was a correct .