7 Things 10 Years Taught Me About Being a Useful Engineer
What actually matters after a decade of shipping code across healthcare, real estate, e-commerce, and startups.
I’ve been writing production code for over a decade. Healthcare platforms, rental marketplaces, consumer products, community tools. The domains change but the lessons compound. These are the shifts that changed how I work, and more importantly, how I think about what’s worth working on.
1. The business never comes back and tells you something mattered. You have to go find out.
The old mode: Get a ticket, do the ticket, list tickets in your review as proof of value. Assume that because the business asked for it, the work had inherent value.
The shift: The work only has value if it produces an outcome, and nobody is going to hand you that information. You have to go looking. Check the data after you ship. Understand who’s using it and whether the thing you built actually moved a number that matters.
What this looks like now: Before starting work, I ask what outcome we’re trying to produce. After shipping, I check whether it happened. This is how I’ve caught things like: “only four people are using this feature, and we’re about to put four engineers on iterating it.” That’s a conversation worth having before the sprint starts, not after.
2. Logging, monitoring, and tracking are not afterthoughts. They’re part of the feature.
The old mode: Ship the feature. If something breaks, react to it.
The shift: If something is important enough to build, it’s important enough to know when it breaks. And “know when it breaks” means logging for engineering (so you can set up alerts and respond), tracking for business (so stakeholders can see what’s happening), and runbooks for on-call (so the next person knows what to do at 2am).
What this looks like now: Every feature I ship includes: what are the critical failure points, what notifications need to exist, and what does the on-call engineer need to know if this goes sideways. I think about this before I write the first line of code, not after. I’ve walked into codebases where zero alerting existed for business-critical workflows like signup and appointment creation. The team was fully reactive. Nobody knew something was broken until a user complained. That’s not a monitoring problem; it’s a cultural one. And it’s fixable, but only if someone decides to fix it.
3. Don’t be a yes-man. Be the person who supplies evidence.
The old mode: The business says build it, you build it. Saying no feels argumentative.
The shift: Providing evidence for why something should be prioritized differently is not being argumentative. It’s being useful. Engineers sit in a unique position: we can see where the quick wins are, where effort is being wasted, and where a small investment could net a disproportionate return. But only if we’re actually looking at the data and speaking up.
What this looks like now: When someone asks me to work on something, I look at the context. How many people does this affect? What’s the opportunity cost? Is there a faster path to the same outcome? I generate my own ideas for what the business should invest in, backed by evidence, not just gut feel. Some of my most impactful work started not from a ticket, but from looking at the product and seeing what was missing. Users couldn’t do something basic, I built a quick implementation to validate the need, and it became a core company goal the following quarter. That didn’t come from a backlog. It came from paying attention.
4. Ship fast, learn, iterate. Perfect code loses to the market.
The old mode: Spend weeks architecting the “right” solution. Try to get it perfect before anyone sees it.
The shift: By the time you’re done perfecting something, you might find out it wasn’t worth building. The fastest way to know if something is good is to put it in front of people. Time spent analyzing what you’ve built is almost always more valuable than time spent speculating about what you should build.
What this looks like now: I focus on finding the real minimum viable version, which takes more conversation and collaboration than people expect. Then ship it, measure it, and iterate with actual data. This is pragmatism, not recklessness. You’re making deliberate tradeoffs about where to invest depth.
I’ve seen this play out in both directions. A legacy app migration that was quoted at over two weeks got delivered in two days by simplifying the scope to what actually mattered. A conversion tracking system pivoted from a three-week plan to a rapid MVP when fundraising timelines changed. In both cases, the constraint wasn’t skill. It was willingness to cut scope to the thing that was actually needed right now.
5. Staying inside your team isn’t leadership. Working cross-functionally is.
The old mode: Stay within your team’s nucleus. Do your work well. Hope someone notices.
The shift: The more stakeholders involved in what you’re working on, the more important the work tends to be, and the more it signals that you’re operating at a leadership level. Impact doesn’t come from being a great code monkey inside your team. It comes from being a reason the business is succeeding.
What this looks like now: I actively look for work that cuts across teams and functions. I connect people to each other and connect their problems to solutions that already exist somewhere in the room. A lot of engineering bottlenecks aren’t technical. They’re communication gaps, misaligned priorities, or a team that’s overthinking requirements when they could learn from a first pass.
The design systems I’ve built are a good example. The best ones aren’t just component libraries for engineers. They’re tools that let non-technical staff ship work without filing a ticket. That’s cross-functional impact, not just clean code.
6. You only have so many innovation tokens. Don’t waste them on migrations that could have been features.
The old mode: A big migration feels like important work. New framework, cleaner architecture, better patterns. It feels like progress.
The shift: Every migration has two costs: the obvious one (time, effort, risk) and the hidden one (what you didn’t build instead). At an early-stage startup, that hidden cost can be fatal. A long-term architectural play doesn’t pay out if the business isn’t around next year. You have a limited number of innovation tokens, and spending them on internal rewrites instead of product enhancements that grow the business is a choice you need to justify with more than “the current code is messy.”
What this looks like now: Before committing to any large-scale refactor or migration, I ask: what’s the opportunity cost? What feature or product improvement are we not shipping while we do this? Is the pain we’re solving actually blocking the business, or does it just bother engineers? Sometimes the migration is the right call. But the default should be skepticism, not enthusiasm.
The lesson isn’t “never migrate.” It’s “understand the real cost and find the smallest version that unblocks the business.” If a migration is committed and blocking other work, the right move is to simplify scope and get through it as fast as possible, not to treat it as an opportunity to gold-plate the architecture.
7. A bad abstraction is worse than duplication. DRY is not a religion.
The old mode: Don’t repeat yourself, ever. If two things look similar, abstract them immediately. Duplication felt like a code smell you had to fix on sight.
The shift: You almost never have full context when you first build something. An early abstraction locks in assumptions you haven’t validated yet, and now that half-baked abstraction lives in two (or five) places. When the requirements inevitably diverge, you’re not dealing with a clean shared component. You’re dealing with a Frankenstein that doesn’t work well anywhere, is painful to change, and introduces regression risk every time you touch it because a change in one place ripples to places you weren’t thinking about.
What this looks like now: I ask a different question before abstracting: if these two features become out of sync, what’s the actual cost to the business? Sometimes the answer is high (a terms and conditions component that must stay consistent across surfaces, for example). In that case, the abstraction is justified. But most of the time, the real cost of duplication is low, and the cost of a premature abstraction is high: slower velocity, broader regression testing surface, and fear of touching shared code. I’d rather update something in two places and ship faster than build a fragile shared layer that slows down every team that touches it.
I’ve lived the cautionary tale. A codebase serving over a million monthly active users where components were so tightly coupled that refactoring was effectively impossible. The fix wasn’t better abstractions. It was enforcing feature isolation so shared abstractions were deliberate choices, not accidental coupling. The design systems I’ve built since then follow the same principle: abstractions earned through repeated patterns, not guessed at upfront.
Murph is a senior full stack engineer and co-founder of Mitobyte, a 1,200+ member developer community in Milwaukee.