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.
1. I Stayed With jQuery Because It Was Working (2013 - 2015)
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.
2. I Ignored TypeScript Until a Production Bug Forced My Hand
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:
- 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.
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.
3. I Thought DSA Didn't Apply to Frontend
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.
4. I Hammered Angular's @Input Listeners Without Thinking About the Cost
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.
Then we hit scale.
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?
5. I Overused Directives and Created a Click Listener Explosion
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.
6. I Refactored Code That Wasn't My Ticket — And I'd Do It Again
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.
Final Thought
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.
Until they didn't.
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.
Top comments (0)