There's a moment in writing Go where the type system says no, and interface{} says yes, and you take the yes. I've done it more times than I'd like, and almost every time it was the wrong call dressed up as pragmatism.
The seduction is real. You've got a function that needs to handle "a few different things", and writing out the type for "a few different things" is fiddly, and there's interface{} sitting right there, accepting anything without complaint. You write it, the compiler stops shouting, and you move on feeling clever. What you've actually done is turn off the type checker for that value and promise yourself you'll get the types right at runtime instead, by hand, with type assertions.
Then six months pass. Someone, possibly me, passes in a thing the function was never meant to handle. The compiler can't help, because I told it not to. So instead of a build error I get a runtime panic, or worse, a silent , ok that's quietly false, and a value that goes nowhere with no complaint. The bug surfaces three layers away from the actual mistake, in production, on a Friday.
The regret isn't that interface{} exists. It's a legitimate tool, and there are genuine cases, decoding arbitrary JSON, writing a generic container before generics existed, where you really don't know the type until runtime and you have to handle it there. The regret is how often I reached for it not because I didn't know the types, but because writing them out was mildly annoying. That's a terrible reason to discard compile-time safety.
The cost compounds, too, because interface{} is contagious. Once a value enters a function as interface{}, it tends to stay that way as it's passed along, since the callee can only hand on what it received. So a single shortcut at the boundary becomes a chain of functions that have all quietly given up on knowing what they're holding. Each one type-asserts, each one has its own little , ok branch that's never exercised, and the actual type of the thing is now documented nowhere except in the head of whoever wrote the original caller, who has long since moved teams.
I've also learned to distrust the comment that accompanies it. Whenever I find interface{} in old code there's usually a note nearby saying // can be a string or an int, and that comment is the confession. If you know it's a string or an int, you knew the types, and the compiler could have known them too. The comment is doing the type checker's job in prose, which means it's doing it badly and it's allowed to go out of date.
Generics are landing properly now, and a lot of the cases where I'd have shrugged and typed interface{} are about to become things the compiler can actually check. I'm glad. But I suspect the habit will outlive the excuse, and I'll still catch myself reaching for the empty interface when I'm tired and the type is just slightly tedious to spell out. The tell, I've learned, is when I write interface{} and immediately follow it with a type switch. That pair is me admitting I knew the types all along and chose to check them the hard way.