Things I Stopped Doing After Watching Senior Engineers Work
Senior engineers do not look senior because they write more code. They look senior because they avoid the expensive mistakes everyone else…
Things I Stopped Doing After Watching Senior Engineers Work
Senior engineers do not look senior because they write more code. They look senior because they avoid the expensive mistakes everyone else keeps repeating.

Things I Stopped Doing After Watching Senior Engineers Work
Senior engineers are not always the loudest people in the room.
They do not always write the most code.
They do not always reach for the newest tool.
Most of the time, what makes them different is what they refuse to do.
I used to think seniority meant moving faster, knowing more patterns, answering every question, and turning every unclear requirement into code as quickly as possible. Then I started watching how strong engineers actually worked.
They paused more.
They asked sharper questions.
They deleted more code than they added.
They did not panic when production broke.
They did not treat every clever idea as a good idea.
The difference was not magic. It was judgment. They had learned, usually through painful experience, which behaviors create hidden debt, slow debugging, fragile systems, and exhausted teams.
These are the things I stopped doing after watching senior engineers work.
I Stopped Changing Code Before Understanding the Failure
The first bad habit I noticed was how quickly most developers start changing code.
A bug appears. A test fails. A customer reports something strange. The developer opens the file, guesses the likely cause, changes a condition, reruns the app, and hopes the problem disappears.
Sometimes it works.
That is why the habit survives.
But senior engineers do something different. They slow down before touching the code. They reproduce the bug. They inspect the input. They check the logs. They ask what changed recently. They try to understand the failure before they start fixing it.
That looks slower from the outside, but it usually saves time.
A real debugging session can collapse because of one early assumption. A developer sees a checkout bug and assumes the payment provider failed. They change retry logic. The bug remains. They add more logs. The bug remains. Two hours later, someone notices the frontend sent an empty customerId because a form field was renamed.
The payment code was never the problem.
The better approach is to make the first step evidence, not action.
Bug:
Checkout fails for saved-card users.
Known:
- New cards work
- Saved cards fail
- Payment provider receives no request
- Backend returns validation error before provider call
Next check:
Inspect request payload from frontend for saved-card flow.
That small note changes the debugging path. It keeps the investigation honest.
Senior engineers are not slow because they lack confidence. They are careful because they know confidence without evidence is expensive.
I Stopped Treating Clever Code as Better Code
Clever code feels good when you write it.
It compresses logic. It makes the file shorter. It shows that you know the language. It creates that small feeling of technical pride where a normal solution becomes something “elegant.”
Then someone else has to debug it.
That is where the cost appears.
Senior engineers are usually suspicious of cleverness. Not because they cannot write clever code, but because they know clever code often optimizes for the author and punishes the reader.
A clever abstraction may turn this:
await checkPermission(user, invoice);
await approveInvoice(invoice.id);
await writeAuditLog(user.id, invoice.id);
await notifyCustomer(invoice.customerId);
Into this:
await runAction("invoice.approve", { user, invoice });
The second version looks cleaner. It may even be the right choice if the pattern is stable and well understood. But if runAction hides permissions, validation, audit behavior, notification rules, feature flags, and side effects, the code becomes harder to investigate.
When approval fails, nobody knows where to look first.
Senior engineers do not ask, “Can I make this shorter?”
They ask, “Will the next developer understand what matters here?”
That question changes the code.
Simple code is not anti-abstraction. Good abstraction reduces repeated decisions and makes systems easier to use. Bad abstraction hides the decisions people need to inspect when something breaks.
I Stopped Solving Problems Before Clarifying Them

A lot of wasted engineering starts with unclear requirements.
Someone says, “We need better permissions.”
A developer hears, “Build a role system.”
Someone says, “The dashboard is slow.”
A developer hears, “Add caching.”
Someone says, “Users are confused.”
A developer hears, “Redesign the flow.”
Senior engineers do not jump that quickly. They ask what the problem actually means.
Better permissions for whom? Admins, managers, support agents, external customers? Which action is currently unsafe? Which user can do something they should not? Is the issue access control, visibility, auditability, or approval workflow?
Those are different problems.
If you solve the wrong one beautifully, you still failed.
I have seen teams build large permission systems because nobody clarified the real issue. The business only needed one role to approve refunds above a certain amount. Instead, the team built configurable policies, nested roles, inheritance, UI permission previews, and migration scripts. Six months later, nobody wanted to touch it.
The senior move would have been less impressive and more useful:
Actual requirement:
Managers can approve refunds up to $500.
Finance admins can approve refunds above $500.
All approvals need audit logs.
Not needed now:
Custom role builder.
Nested permission inheritance.
UI permission editor.
That kind of clarity prevents architecture from becoming a monument to misunderstood requirements.
Senior engineers protect the team from building too early. They know code is expensive once it becomes real.
I Stopped Adding Tools to Hide Weak Thinking

Developers love tools.
New frameworks, new state managers, new ORMs, new logging platforms, new AI helpers, new queue systems, new deployment dashboards. Tools can be useful. Sometimes they are exactly what the system needs.
But tools also make weak thinking look active.
A slow API does not automatically need caching. It may need a better query. A messy frontend does not automatically need a new state manager. It may need clearer ownership. A broken deployment pipeline does not automatically need a new CI platform. It may need simpler environments and better secrets management.
Senior engineers are careful with tools because every tool becomes part of the system the team must understand.
A team adds a queue to solve slow checkout. Now they have retries, ordering, idempotency, monitoring, dead-letter queues, duplicate events, and delayed failure visibility. Maybe that is the right tradeoff. But if the original issue was one slow database query, the queue did not solve the root problem. It just moved the pain somewhere more distributed.
The better question is not, “What tool can solve this?”
The better question is, “What is the smallest change that addresses the real failure?”
Sometimes the answer is boring:
CREATE INDEX idx_orders_customer_created
ON orders (customer_id, created_at);
That will not impress anyone in an architecture meeting.
It may save the system.
Senior engineers do not avoid tools. They avoid tool-driven thinking. They know every new dependency adds learning cost, failure modes, upgrade risk, security surface, and operational burden.
I Stopped Treating Pull Requests Like a Form Filling Exercise

A pull request is not just a place where code waits for approval.
It is where shared understanding either happens or dies.
I used to think a good pull request was mostly about passing tests and keeping the diff clean. Senior engineers treated pull requests differently. They used them to explain intent, risk, tradeoffs, and review focus.
A weak pull request says:
Implemented billing changes.
That tells the reviewer almost nothing.
A useful pull request says:
What changed:
Added retry handling for failed invoice webhooks.
Why:
Provider sometimes times out but still completes the event later.
Risk:
Duplicate invoice updates if idempotency check is wrong.
Please review:
- Retry timing
- Idempotency logic
- Audit log behavior
This saves review energy. It tells the reviewer where to look. It turns review from “scan the diff” into “evaluate the change.”
Senior engineers also avoid mixing too many ideas in one pull request. They separate refactors from behavior changes. They do not hide risky logic inside formatting cleanup. They do not make reviewers reverse-engineer the purpose from the file list.
A massive pull request may feel productive to the author. To the reviewer, it often feels like fog.
When reviewers are confused, they either block everything or approve shallowly. Both are bad.
Takeaway: a pull request is not done when the code works. It is ready when another engineer can review it with confidence.
I Stopped Ignoring Names

Naming used to feel like a small thing.
Then I watched senior engineers spend real time on names.
Not because they were perfectionists. Because names are how a codebase explains itself.
A bad name creates repeated confusion. A vague name forces every reader to inspect implementation. A misleading name creates bugs because developers trust the wrong meaning.
Consider this:
const status = getStatus(order);
What does status mean? Payment status? Shipping status? Approval status? Internal workflow status? Customer-visible status?
Now compare it with:
const paymentStatus = getPaymentStatus(order);
const fulfillmentStatus = getFulfillmentStatus(order);
This looks basic, but basic is often what prevents expensive mistakes.
Senior engineers treat names as architecture. They know a system with poor names becomes hard to discuss, hard to debug, hard to test, and hard to change. If two people use the same word to mean different things, bugs are already forming.
This happens constantly in business software. “User” might mean account owner, logged-in actor, customer profile, admin record, or identity provider subject. “Status” might mean database state, UI label, external provider state, or internal workflow step.
A senior engineer will pause and ask, “What exactly do we mean by active?”
That question can prevent weeks of confusion.
I Stopped Writing Logs That Only Prove Code Ran
Most bad logs are just noise with timestamps.
console.log("here");
console.log("response", response);
console.log("done");
These logs help for five minutes during local debugging. In production, they are nearly useless.
Senior engineers write logs to answer future questions. They log request IDs, user or account context where appropriate, important decisions, failure reasons, and boundaries between systems. They avoid logging secrets. They make logs searchable and meaningful.
A weak production log says:
Error processing request
A useful log says:
invoice_approval_failed
requestId=req_92
accountId=acct_18
reason=missing_permission
actorRole=support_agent
That log answers real questions.
What failed? Which request? Which account? Why? Was it a permission issue, validation issue, provider issue, or system error?
Good logs reduce panic. They let developers debug without guessing. They make incidents shorter. They help support teams understand what happened without waking up half the backend team.
But senior engineers also know logs can create risk. They do not dump full request bodies, tokens, cookies, passwords, API keys, or personal data into logging systems just because it is convenient.
The better approach is intentional logging:
logger.warn("invoice approval denied", {
requestId,
invoiceId: invoice.id,
actorId: user.id,
reason: "missing_permission"
});
That is not fancy. It is useful.
Takeaway: logs are not useful because they exist. They are useful when they answer a specific question safely.
I Stopped Making Everything Reusable Too Early

Reusable code feels responsible.
Nobody wants duplication. Nobody wants five slightly different versions of the same logic. Nobody wants a codebase full of copy-paste.
But senior engineers know that premature reuse is one of the fastest ways to create bad coupling.
Two pieces of code can look similar today and evolve differently tomorrow. If you merge them too early, you force unrelated behaviors to share one abstraction. Then every new requirement adds a flag.
sendNotification(user, {
type: "invoice",
urgent: false,
skipEmail: isMobileOnly,
includePdf: true,
fallbackToSms: false,
source: "billing"
});
The function started as reusable.
Now it is a negotiation table for every feature team.
Senior engineers wait for the pattern to prove itself. They tolerate small duplication when the domain is still unclear. They prefer repeated clear code over one generic function full of hidden branches.
This does not mean duplication is good. Repeated business rules can create serious bugs. Shared validation, response formats, logging helpers, error handling, and security checks often deserve abstraction.
The difference is timing and reason.
Abstract when the behavior changes for the same reason.
Do not abstract only because the code looks visually similar.
Takeaway: reuse is valuable when it reflects shared meaning, not just shared shape.
I Stopped Acting Like Speed Is Only About Typing Faster

Junior developers often think fast engineers type fast.
Senior engineers show that real speed comes from fewer wrong turns.
They spend more time understanding the problem. They ask clarifying questions. They reduce scope. They choose boring designs. They split risky work. They write notes. They create clean review paths. They avoid unnecessary tools. They fix root causes instead of symptoms.
From the outside, this can look slower.
Then the project finishes with fewer surprises.
A rushed developer may build a feature in two days and spend four days fixing edge cases. A senior engineer may spend half a day clarifying the flow, then implement the smaller correct version in one day, with fewer bugs and less rework.
That is real speed.
Speed is not how quickly code appears. Speed is how quickly the team reaches a correct, maintainable outcome.
A common example is API design. A rushed endpoint returns whatever the frontend needs today. Later, mobile needs another shape. Reporting needs another field. Errors are inconsistent. Clients start parsing strings. The team pays for the shortcut repeatedly.
A senior engineer slows down enough to define the response contract early:
{
"success": false,
"data": null,
"error": {
"code": "INVOICE_NOT_APPROVABLE",
"message": "Invoice cannot be approved in its current state."
},
"meta": {
"requestId": "req_123"
}
}
That small structure prevents future confusion.
Takeaway: speed is not skipping thinking. Speed is avoiding rework.
I Stopped Trying to Be the Smartest Person in the Room
This may be the most important one.
Strong senior engineers do not need every idea to be theirs. They do not win discussions by overwhelming people. They do not turn code review into a performance. They do not use knowledge as a weapon.
They make the team sharper.
They ask questions that expose risk. They explain tradeoffs. They let junior developers own real work. They correct without humiliating. They share context instead of hoarding it. They know that being indispensable can become a team failure.
A developer who fixes every problem alone may look valuable, but they can also create dependency. If nobody else understands the system, the team is fragile. If every decision waits for one person, that person becomes a bottleneck. If knowledge stays private, speed depends on availability.
Senior engineers multiply judgment.
They say things like:
Here is why I would avoid this design. It works for the current feature, but it makes permissions harder to reason about later. What if we keep the rule explicit for now and revisit abstraction after the second use case?
That kind of feedback teaches the decision, not just the answer.
The best engineers are not impressive because they make everyone else look junior. They are impressive because people become better around them.
Takeaway: seniority is not proving you are smart. It is making better decisions easier for the team.
Conclusion
After watching senior engineers work, I realized most of their value was not in the dramatic moments.
It was in the avoided mistakes.
They did not rush into code before understanding the failure.
They did not worship cleverness.
They did not add tools to hide unclear thinking.
They did not treat reviews, logs, names, or tests as small details.
They did not turn every similar line into an abstraction.
They did not confuse speed with motion.
The best engineers are not just better at writing code. They are better at protecting the system from unnecessary damage.
That is the quiet part of seniority.
Not more ego.
Not more complexity.
Not more performance.
Better judgment.
If this hit hard, share it with a developer who is trying to grow beyond just writing more code.
Call to Action
👏 Found it useful? Clap. 💬 Got thoughts? Comment.
Read Next
If you liked this idea, you may enjoy these too:
The Dirty Code Pattern Developers Keep Rebuilding
Link Here: **Read**

메타데이터
- post_id
- 59a319f4620f
- slug
- things-i-stopped-doing-after-watching-senior-engineers-work-59a319f4620f
- url
- https://medium.com/skillstuff/things-i-stopped-doing-after-watching-senior-engineers-work-59a319f4620f
- canonical_url
- https://medium.com/skillstuff/things-i-stopped-doing-after-watching-senior-engineers-work-59a319f4620f
- author_url
- https://medium.com/@codetune
- status
- ok
- fetched_at
- 2026-07-10 21:23:43