Every Go codebase I've worked on has an interface{} in it that someone, often me, swore was temporary. It never is. You reach for it because you don't yet know the shape of the data, and then the shape arrives, and the interface{} stays because removing it means touching twelve call sites.
The one I regret most was a config layer. We were loading values from a few sources, env, flags, a YAML file, and rather than model the thing properly I made the central store a map[string]interface{}. It felt flexible. What it actually was, was a promise to do type assertions forever, at the worst possible moment, which is runtime, in production, on a Friday.
The failure mode is always the same. Somewhere downstream you write v.(int) and the value is a float64 because it came through JSON, or it's a string because someone quoted it in the YAML. The compiler can't help you. It doesn't know what's in the box. You find out when the assertion panics, or worse, when you used the comma-ok form, swallowed the failure, and silently got the zero value. A timeout of 0. A retry count of 0. Lovely.
What I do now is push the conversion to the edges. Parse the messy input into a real struct as early as possible, validate it there, and let the rest of the program work with types the compiler understands. The empty interface, if it has to exist at all, lives in one small adapter that I can read in full and test exhaustively. The blast radius shrinks to a single file.
There are legitimate uses, of course. fmt.Println takes ...interface{} and that's exactly right, it genuinely doesn't care what you give it. Encoding libraries need it. The standard library is honest about where the type information genuinely isn't available. The trouble is that "I don't know the type yet" and "the type is genuinely unknowable" feel identical when you're writing the code, and only one of them justifies the cost.
If I had a rule to give my younger self, it would be this: an interface{} is a deferred decision, not a solved problem. Every one you leave in is a small loan against future-you, and future-you pays it back with a stack trace. Generics aren't here yet, and I'd love them, but most of my empty interfaces weren't waiting for generics. They were waiting for me to sit down and name the thing.