Start here. This is the direct spoken answer to practice first.
Why this question matters
Opening a database connection is expensive, so ADO.NET reuses physical connections. During incidents, a pool timeout often means application work is holding scarce connections too long rather than that the database rejected a login.
Opening a SqlConnection normally borrows a physical connection from a client-side pool, and closing or disposing it returns that connection for reuse. Pools are separated by exact connection configuration and other context such as identity, so small connection-string differences can fragment them. If the maximum pool size is reached and no connection returns before the connection timeout, the request fails. I open as late as practical, dispose deterministically with using or await using, and do not keep connections or readers across unrelated work.