Five years ago, I applied for a Senior Software Engineer role. I wasn't entirely sure if I wanted the job, but the product caught my interest. After passing the online assessment, I was invited to the first round of interviews — and to my surprise, the CTO was on the attendee list.
The meeting began with quick introductions, and then the CTO took the lead. She didn't waste time: "We think your experience isn't enough for a Senior role, but we still wanted to see how you would do." I was a little offended. I was young, but I had already written a lot of code and worked at several companies. This felt like my chance to prove her wrong.
I braced myself for tough technical questions — dynamic programming, tricky data structures, maybe some bit manipulation. Instead, the first question was: "What is technical debt?" I froze. I gave a one-line textbook answer and waited, expecting the real test to begin. But she pressed on: "What do you do when deadlines are missed? How do you prioritize tasks? How do you handle conflict in a team?" I couldn't hide my frustration. Where were the hard technical problems, the system designs, the algorithm puzzles?
After the interview, I was convinced she didn't know how to evaluate engineers. But looking back now, I realize she knew exactly what she was doing. She wasn't hiring just a coder. She was looking for someone who could navigate trade-offs, lead people, and think beyond the code itself. I wasn't ready then. But that conversation left me with a lasting realization: being "senior" isn't about mastering every protocol or memorizing every algorithm. It's about judgment, working with people, making decisions, thinking long-term, and developing the subtle skills nobody teaches you in school.
Over the years, I've made mistakes, worked with incredible teams, and learned lessons the hard way. I once purged a production database. I once argued for days about a design that never shipped. I once ignored a teammate's warning and caused a production crash. Each mistake left a mark, but also a lesson. Today, I want to share the ones that, looking back, mattered most on the path to becoming a Senior Engineer.
To illustrate these lessons, I'll use a recurring character — Eddie. He isn't a real person or someone I've worked with, but a fictional stand-in inspired by my younger self and the situations I've experienced. Eddie reflects the mistakes I made and the lessons that shaped me.
Reasons Over Rules
Once, we needed to add new fields to a DynamoDB table in production. Eddie, a bright new joiner, proposed a heavyweight migration — replicate the table, sync with streams, then cut over after a week of testing. The plan sounded complete, but he hadn't considered simpler options, so I asked, "Why not update the existing records with a script?" His answer was blunt: "That is dangerous. You shouldn't update a prod database", with no reasoning or data.
Here's the thing: in that specific system, the database didn't serve customer-facing traffic; it powered offline jobs. We could throttle updates, monitor metrics, and roll back if necessary, so the real risk was low compared to the complexity of his plan. We built a simple background updater instead, finished in days, not weeks, and it worked.
Eddie's approach reflected a common trap: treating "best practices" as absolute truths. Never touch production. Microservices are always better. Monoliths are bad. Rules like these sound safe, but in reality, they are context-dependent. True seniority isn't about having a larger catalog of rules. It's about reasoning. Always try to explain why a decision makes sense in a given situation and weigh risks against trade-offs.
Don't have assumptions, verify
One of the most humbling lessons of my career came during outages. When systems fail, it's easy to jump to conclusions: "It must be the database," "The new deployment caused it," "It's probably transient." But assumptions waste precious hours — and when production is burning, every minute counts.
I once spent half a day convinced a feature flag rollout had broken our service. I rolled it back, combed through the code, and chased dead ends. Only later did we uncover the real cause: a dependency update shipped in the same deployment. My tunnel vision had cost the team valuable time.
The best engineers don't rely on guesswork. They trust data, and use their experience to guide where to look first — but they always verify. The real skill isn't having the "right hunch"; it's having the discipline to check logs, compare graphs, reproduce issues, and ask teammates for sanity checks. Verification solves problems faster and builds credibility. Nothing erodes trust quicker than confidently blaming the wrong thing.
Question the Good News
Eddie once rushed to my desk, excited: "our API latency had dropped 20% after my recent changes" I was surprised because his task was just a simple refactor — it shouldn't have affected latency at all. On the surface, it looked like a win. But when something looks too good to be true, it often is. Instead of celebrating, I asked him, "Why did it drop?" He was so focused on the positive outcome that he hadn't stopped to question it.
After digging deeper, we discovered the real story: he had accidentally removed retry logic in our downstream calls. (Yes, it even passed through PR reviews.) The latency improvement wasn't a performance boost, it was because failed requests weren't being retried. Behind the shiny numbers, silent failures were piling up, until alarms went off the next day.
The bigger lesson here is: always treat sudden miracles with skepticism. When metrics improve overnight, the first question should always be, "What did we break?" Healthy skepticism prevents teams from celebrating false wins. It reinforces an essential truth: data is only valuable when you understand the why behind it.
Mechanism over good intentions
In engineering, I've lost count of how many times I've heard, "We'll just remember to do X next time." It always comes from good intentions, but good intentions don't protect systems. People get distracted, tired or even pressured by deadlines. I once saw a critical incident triggered because someone forgot to run a post-deploy script. It wasn't that the person was careless; but the process was fragile.
The strongest teams don't rely on memory or promises. They build mechanisms to make mistakes less likely and recover faster. Automated checks, CI/CD pipelines, feature flags, two-person approvals, monitoring alerts — these aren't "nice to haves," they are guardrails. Instead of hoping someone remembers to double-check a config, you design the pipeline so the config can't be deployed without validation. Instead of trusting someone to manually run a script, you make the script part of the deployment flow.
Senior engineers design systems where the safe path is also the easiest path. Mistakes will happen, that's inevitable. What defines a strong engineering culture is whether your system catches those mistakes before your customers do.
The Discipline of Saying "No"
Eddie once joined a meeting with one of our clients. They were using our APIs for a critical system and wanted to introduce a new filtering logic that required pulling data from multiple sources. Instead of implementing the logic on their side, they proposed offloading it to our backend. After a long discussion, they convinced Eddie to agree. The plan was that they would contribute the changes as an away team, but the logic would still live in our systems.
When Eddie shared the outcome the next day, every senior engineer on the team pushed back. The clients didn't have strong reasons for not making the change on their side, they simply wanted to shift the complexity onto ours. Eddie had to go back, explain the decision, and clarify that the proposal wasn't accepted. The back-and-forth caused delays and confusion, but it also left him with an important lesson: sometimes the hardest but most valuable thing you can do is say "no."
I'll admit, this has been one of the toughest lessons in my own career as well. Senior engineers aren't the ones who say "yes" to everything. They are the ones who protect their teams from unnecessary complexity, push back when trade-offs don't make sense, and know that not every request deserves to be accepted.
Growth Starts With Ownership
Early in my career, I leaned heavily on senior teammates. Whenever a decision came up, I defaulted to them, thinking, "They know better than me." It felt safe. I had to be certain and confident before making the decision.
But the truth is, engineering is full of uncertainty. Rarely do we have perfect information, and waiting for it leads to paralysis. Seniors aren't people who always have the right answer; they are people who make informed calls, weigh trade-offs, and take responsibility. When those decisions turn out to be wrong, they don't deflect blame. Instead, they own the outcome, learn, and adjust.
From Protector to Mentor
When I first began mentoring juniors like Eddie, my instinct was to shield them from failure. I'd leave long review comments, walk them through every design detail, or even step in and write the code myself. It felt efficient in the moment, but it slowed their growth. Protecting someone from mistakes may prevent short-term pain, but it also deprives them of the lessons that only come from firsthand experience.
With time, I realized my role wasn't to prevent errors but to create a safe space where mistakes could happen without catastrophic consequences. Real growth comes from stumbling, recovering, and carrying those lessons forward. The ultimate measure of leadership is whether the team can thrive without you. If you've built a culture where people learn through safe failure, then you've done your job as a mentor.
Simplicity Scales, Complexity Breaks
One of the hardest lessons for ambitious engineers to learn is that simple almost always beats clever. In uncertain situations, it is tempting to design for every possible scenario, "future-proofing" the system with abstractions and extensibility. But more often than not, those extra layers turn into dead weight.
I once watched a team build a sophisticated plugin framework for "future integrations." It had its own configuration DSL and multiple extension points. Three years later, not a single plugin had ever been written! But every change in the system had to navigate around that unused complexity. What looked like foresight in the moment turned into drag over time.
Try to avoid premature complexity at all costs. Simple solutions aren't just easier to write; they are easier to test, maintain, and onboard new engineers onto. They age gracefully because they carry fewer assumptions.
Every System Breaks Eventually, Be Prepared
No system is truly fault-tolerant. Code is alive — it evolves, integrates with new dependencies, and adapts to changing requirements. Each modification, no matter how small and well tested, increases the chance of introducing failures.
I once worked on an API that returned the list of projects a user owned. We never documented any guarantees about ordering, but because of the way our SQL query was written, results always happened to come back in ascending order by creation date. Over time, some clients quietly built logic that relied on that order. When we upgraded the database and optimized the query, the ordering behavior changed, and all tests passed. The next day, we were paged: downstream systems were failing. Nothing was "wrong" with our code, but clients had built assumptions on API responses that we were unaware of.
That's the reality of distributed systems: sometimes you're the one breaking a dependency, and sometimes you're the one depending on an unstated behavior. Both roles carry risk. Senior engineers anticipate this by communicating guarantees clearly, versioning APIs carefully, and setting up monitoring that catches unexpected shifts in behavior. Tests are valuable, but they're not a shield. Given enough time, enough traffic, and enough change, every system will break. The question isn't if — it's when, and how prepared you are to respond.
Embrace Change and Adapt
The longer you spend in this industry, the clearer it becomes that change is constant, and the pace is only accelerating. In today's world, LLMs have already become part of everyday workflows. They are used to summarize information, answer questions, and assist with routine tasks (including coding). Is there hype surrounding them? For sure, yes. But it would be a mistake to assume they will simply fade away and leave us with the old ways of working. These tools will continue to improve and gradually take on more responsibilities.
Unlike the other lessons I've shared here, this one doesn't come with a classic story or decades of battle-tested examples. We're still at the very beginning of figuring out the long-term impact of LLMs, which means the patterns aren't fully written yet. What I can say is that I've already seen them provide small but meaningful gains; generating service status reports, scanning through logs to link metrics to incidents, writing code/tests (always peer-reviewed), and even highlighting potential security concerns during code reviews. They're not a replacement for engineering judgment, but when used thoughtfully, they can speed things up.
That is why embracing change here doesn't mean blindly trusting AI-generated code, nor assuming it solves every problem. It means staying informed, experimenting carefully, and integrating what genuinely adds value while being mindful of risks like reliability, bias, or over-reliance. Engineers who learn to collaborate effectively with LLMs will free themselves to focus on higher-level problems, and in my opinion, they'll be better prepared for whatever role this technology grows into next.
Closing Notes
At the end of the day, becoming a senior software engineer isn't about memorizing every algorithm, mastering every framework, or pretending to have all the answers. It's about judgment, humility, and the ability to adapt when things don't go as planned. It's about pausing to ask why, verifying instead of assuming, and putting guardrails in place so the team doesn't have to rely on memory or luck. Most of all, it's about becoming someone others can trust — when systems fail, when trade-offs get messy, or simply when people need guidance.
None of us get there without mistakes. I certainly didn't. What matters is what you learn from those moments, and how you pass that knowledge on to others. In the long run, your real job is to grow people who can one day take your place. That's what leadership looks like. If there's one message I hope you take from this article, it's that seniority is not a title — it's a mindset. And it's built slowly, day by day, decision by decision.
Disclaimer: The content of this article is based on my personal experiences and reflections. It should not be interpreted as representing the views of my current employer or any other organization I have worked for.