I think the problem comes from applying “clean code” as a standard pattern.
As demonstrated in the example, extensibility costs in performance, and also sometimes in comprehension. If we apply the “clean code” rules as a matter of course, we pay this price always.
In my opinion, we should use interfaces etc at module boundaries only.
What is adding a polygon going to do? Either back to the 'bad' interface to hide variable object size, extend to a tagged union with a size of the biggest datastructure - hurting cache performance each time it grows -, or an involved Object of Arrays structure - not good for clarity but great for performance.
All while having to remember which field, width or height, to use for the circles radius.