Problem statement
Design the object model and public APIs for a generic, thread-safe object pool: a container that hands out a bounded number of expensive-to-create objects (think database connections, parsers, or large reusable buffers) and takes them back for reuse instead of creating a fresh one every time. A caller borrows an object, uses it, then returns it; the pool keeps idle objects ready so the next borrow is cheap.
Operating context. A single in-process library used by many application threads on one JVM/runtime. The pool is parameterized by the type of pooled object and never assumes what that object is. Object creation and destruction are delegated to a caller-supplied factory. The pool enforces a maximum size; when all objects are checked out, a borrow either blocks up to a caller-specified timeout or fails. Objects are validated before being handed out and reset to a clean state on return so no caller ever sees another caller's leftover state. Dozens of threads may borrow and return concurrently.
Out of scope. The concrete pooled resource itself (you design the pool, not the database driver), any network or I/O protocol, persistence or metrics export, distributed/multi-process pooling across machines, and eviction of long-idle objects on a background timer (single-node, in-process only).
What to produce. The class hierarchy and interfaces (the generic Pool contract, the pluggable object factory, and any supporting value objects), the public method signatures each type exposes, and the object lifecycle states with their transitions. Be explicit about: the borrow / return protocol and the object's states (idle, in-use, invalid, destroyed); how the factory creates, validates, resets, and destroys objects; how the maximum-size cap plus blocking-with-timeout is enforced under contention; what happens when a borrowed object fails validation or is returned broken; and how a caller that never returns an object is handled.
Functional requirements
- Borrow returns a ready-to-use pooled object, creating one via the factory if none are idle and the cap is not yet reached.
- Return hands a borrowed object back to the pool, which resets it and marks it idle for the next borrower, or destroys it if it is broken.
- When the pool is at its maximum size and all objects are checked out, a borrow blocks up to a caller-supplied timeout and then fails cleanly rather than exceeding the cap.
- Every object is validated before being handed to a borrower; an object that fails validation is destroyed via the factory and replaced rather than returned to the caller.
- The pool is generic over the pooled type and delegates create / validate / reset / destroy to a caller-supplied factory, knowing nothing about the concrete object.
- Expose current counts (idle, in-use, total) so a caller can observe pool utilization.
Non-functional requirements
- Thread-safe: concurrent borrow and return from many threads never hand the same object to two borrowers nor exceed the maximum size.
- Borrow and return are O(1) amortized in the number of pooled objects; no scan of all objects per operation.
- Generic over the pooled type with no compile-time dependency on any concrete resource class.
- The object factory is pluggable — swapping create / validate / reset logic must not touch the borrow / return core.
- Unit-testable without real resources or a wall clock: factory and timeout clock are injectable, and blocking/timeout behavior is deterministically testable.
- No object leaks a previous borrower's state: reset-on-return (or reset-on-borrow) is guaranteed before any reuse.
Topics
- System Design LLD
- Concurrency Locks
- Patterns Factory
- Extensibility
- Statemachine