When I look back at mistakes I have made in my professional life, one category that rises to the top of the list is writing code that is too abstract and too heavily optimized for my use case. Although abstraction is a necessary part of writing good code, it is easy to take it to an unhealthy extreme, interfering with readability and making what should often be simple business logic more difficult to understand. The same can be said for optimization: it is important to write performant code, but writing a convoluted mess to eke out another 0.0005% increase in speed is almost always wrong in the real world. Programming is about tradeoffs, and that often means making small performance sacrifices for maintainability and clarity.
As programmers, we are taught to abstract common functionality out into separate functions, macros, or other language constructs. This is undeniably a good thing — with too little abstraction, programs become unwieldy, and the probability of regressions increases as developers have to remember to make the same changes in more and more places when modifying the code. However, abstraction can go too far: very abstract code, with myriad functions calling each other, rapidly becomes nearly impossible to read without great effort, even for experienced professionals. I have at times, to my great chagrin, discovered that I could barely read my own code from several months ago, due to over-abstracting it when I first wrote it. Do not repeat this mistake: when splitting up your functionality between functions, ask yourself whether the resulting code will be significantly more obscure than a slightly less "perfect" option.
The same rule applies with optimization, although its practical effect may be different: there comes a point where making your code a tiny bit faster or using a little bit less memory is not worth the complexity of the additional logic. There certainly are use cases where squeezing every last ounce of performance out of your code is a real necessity, but they are not the norm, and if you are working on a project where that is the case, you will know. Otherwise, do not worry about creating an extra object, re-sorting a short list, or iterating through a loop an extra time or two if the alternative would be to write code that is harder to read.
Now, my goal here is not to complain about abstraction or optimized code in general — both are very important elements of programming as a trade, and should be practiced where needed. However, be careful not to design something too perfect to understand. Code should be written to be read, not just to be run, and runnable but unreadable (and thus unmodifiable) code is as good as useless when the business logic changes. Write for future use cases, not just the current one.
Finally: we have all fallen into this trap at one point or another, and beating yourself up when you make this kind of mistake is counterproductive. However, be sure to take the lesson to heart, and prioritize readability in code you write in the future.