Tools
Tools: Mistakes I Made as a Frontend Engineer (And What They Actually Cost Me)
2026-02-28
0 views
admin
1. I Stayed With jQuery Because It Was Working (2013 - 2015) ## 2. I Ignored TypeScript Until a Production Bug Forced My Hand ## 3. I Thought DSA Didn't Apply to Frontend ## 4. I Hammered Angular's @Input Listeners Without Thinking About the Cost ## 5. I Overused Directives and Created a Click Listener Explosion ## 6. I Refactored Code That Wasn't My Ticket — And I'd Do It Again ## Final Thought I've shipped a 2s → 0.2s FCP improvement.
I've cut build times from 20 minutes to 90 seconds by making the right framework call. I've led an Angular migration across 500+ components with a 12-engineer team. None of that changes the fact that I made avoidable mistakes early in my career.
Some from ignorance, some from arrogance, some from nobody being around to tell me otherwise. This is for junior and mid-level engineers who are still in the window where these mistakes are correctable. In college, I built personal projects with jQuery. It worked. I never had a reason to look further. No one around me, faculty included knew much more than that. So I didn't push. That attitude this is enough, I'm getting results — followed me into my first job. When the industry had already moved to Angular and React, I was still thinking in jQuery terms. I had to learn a component-based mental model on the job, under delivery pressure, without the luxury of focused learning time. What it actually cost: Not the job. But the first year was harder than it needed to be. I was catching up on fundamentals while simultaneously trying to ship features. The lesson: Curiosity in college is free. It costs nothing to ask what are teams actually using in production right now? That question alone would have changed my trajectory by 12 months. Early in my career, TypeScript felt like overhead. I was writing JavaScript, things were working, and adding types felt like extra ceremony with no visible return. Then I passed a wrong type in production. It wasn't a subtle edge case. It was a runtime failure that reached users. That was the moment I stopped telling myself TypeScript was unnecessary overhead. What I found after actually learning TypeScript: The lesson: TypeScript isn't ceremony. It's a communication layer between you, your teammates, and future-you. The overhead is front-loaded. The payoff compounds. For years, I treated Data Structures and Algorithms as a hiring filter — something you studied for interviews and forgot immediately after. Frontend is UI, right? State management, component trees, CSS. What does Big O have to do with any of that? Then I hit a real performance problem. We had a filtering operation running on a large dataset. The existing code used array iteration and nested loops for comparisons — the team had written it that way, it worked, and nobody questioned it. I replaced the comparison logic with a Set. That single change gave us a 200ms performance gain. Users notice 200ms. It's the difference between an interaction feeling instant and feeling sluggish. I also used recursion extensively in production — tree traversal for nested UI structures, recursive rendering logic, hierarchical data transformations. These aren't academic exercises. They show up in real frontend work regularly. The lesson: You won't use Dijkstra's algorithm in a React component. But you will absolutely encounter problems where knowing the right data structure is the difference between a fast product and a slow one. DSA teaches you to think about operations, not just write code that works. In Angular, @Input with ngOnChanges is the obvious way to react to parent-to-child data flow. I used it heavily. It was the path of least resistance, and for a while, nothing visibly broke. The symptoms came gradually, then all at once: keystrokes appearing in bursts after a delay, dropdowns not opening, the sidebar freezing mid-interaction, the browser throwing a page crash dialog asking users to either wait or exit. Users couldn't reliably interact with the product. The root cause: too many change detection cycles firing simultaneously across too many components, all triggered by input changes cascading through the tree. The fix required going back and auditing the architecture — determining what actually needed to react to changes versus what was reacting unnecessarily. It was migration work that shouldn't have been necessary. The lesson: ngOnChanges and @Input listeners are not free. Every one you add is a hook into Angular's change detection. At component scale, this compounds. Before wiring up an input listener, ask: does this component actually need to re-evaluate on every parent change, or can this be derived, memoized, or handled further up the tree? Directives in Angular are powerful. I used them extensively for shared behavior, which is correct in principle. The mistake was not thinking about what happens when a directive is instantiated hundreds of times across a large application. I had a directive that listened to click events. It was applied broadly. Each instance created its own event listener. On a screen with many instances, we had hundreds of active click listeners running simultaneously. The user-visible symptom: a simple search bar — no search-as-you-type, just a standard Enter key to trigger an API call — became unresponsive. Users couldn't type. Keystrokes were being swallowed. What should have been a trivial interaction was broken by listener saturation. The fix: replace the distributed listener pattern with a single event source in a service, broadcast via an observable. One listener. One source of truth. The UI became responsive immediately. The lesson: When a directive handles DOM events and gets used at scale, you don't have one listener — you have N listeners. Before writing event-handling logic into a directive, ask whether a centralized service with a single subscription would serve the same purpose with a fraction of the overhead. This one is more complicated. Management's view: refactoring work you weren't assigned is wasted time. It's scope creep. It's not in the sprint. My experience over four years: I refactored proactively whenever I saw something structurally wrong — float-based layouts that should have been flexbox, JS-driven dropdown state that should have been CSS focus-within, array comparisons that should have been Set lookups. Nobody asked me to. I just did it. This happened seven times in four years. And in every single case, within the next sprint, I had a feature to build on that exact component. The float→flexbox refactors were the clearest example. A CSS float layout is brittle. It breaks under theme changes, under translation string length variation, under responsive breakpoints. When I was handed a feature that required layout changes to those components, my prior refactor meant I was working with a clean, predictable structure instead of fighting the existing one. What would have taken 3 days was done in a day. The opportunistic refactoring wasn't wasted time — it was pre-paid time. The sprint where I refactored looked inefficient. The sprint where the feature landed showed the return. Replacing JS-based click listeners on dropdowns with CSS focus-within also reduced the event listener footprint across the application — the same class of problem described in mistake 5, resolved structurally rather than reactively. The lesson: Proactive refactoring is a bet. You're spending time now against uncertain future return. In my experience, if you have enough system knowledge to know a component is going to be touched again soon, it's usually a good bet. The mistake is doing it indiscriminately. The skill is doing it with judgment. Most of these mistakes share a root: I optimized for working now without thinking about cost at scale. jQuery worked. @Input listeners worked. Directives worked. Array loops worked. Frontend engineering at scale is not about making things work. It's about understanding what you're trading when you make a choice — and being honest about when that trade stops being worth it. These mistakes are what actually make you an engineer, not just someone who writes code.
The engineers AI can't replace are the ones who've already made them. Hopefully this is that someone for you. Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse - Refactoring became significantly safer. I could rename, restructure, and move things without hunting for every consumer manually.
- Code review got faster because intent was explicit.
- Reusability improved. Properly typed interfaces forced me to think about contracts before implementation.
how-totutorialguidedev.toaijavascript