All posts

15 Coding Habits Stolen From Senior Engineers

KynodexKynodex
20 min read
15 Coding Habits Stolen From Senior Engineers

None of these patterns are secret. They're not in a framework's documentation, and no senior engineer will hand you a checklist. You notice them the same way — by watching someone debug a production incident calmly while everyone else is panicking, or reading a pull request where the diff is smaller than yours but does more.

These are seven habits that showed up repeatedly across the strongest engineers I've worked with — in code review, in incident response, and in how they structured systems before a single line got written. None of this is proprietary to any codebase or company. It's the kind of thing that gets rediscovered independently by anyone who's shipped and maintained software long enough to feel the cost of not doing it.


1. They Name Things for the Reader Six Months Later, Not for Themselves Right Now

The instinct when you're deep in a problem is to name variables for what makes sense to you, in this moment: data, temp, result2, flag. Senior engineers write names as if they're leaving a note for someone who has zero context — because in six months, that someone is often themselves.

# What most of us write under deadline pressure
def process(d, f=True):
    r = []
    for x in d:
        if x.s == 'active' and f:
            r.append(x)
    return r

# What a senior engineer writes, even under the same pressure
def filter_active_users(users, include_only_verified=True):
    active_users = []
    for user in users:
        if user.status == 'active' and include_only_verified:
            active_users.append(user)
    return active_users

The second version costs maybe fifteen extra seconds to type. It saves the next person — often you — five minutes of re-deriving intent from behavior. That trade is almost always worth it, and the engineers who consistently make it are the ones whose code doesn't need a Slack thread to explain.


2. They Push Error Handling to the Boundary, Not Every Function

A pattern that shows up constantly in junior code: defensive try/except blocks wrapped around every single function, "just in case." This feels responsible. It usually isn't — it scatters error handling logic throughout a codebase and makes it genuinely hard to know where an error will actually surface and get handled meaningfully.

Senior engineers tend to let errors propagate through the internal call stack and catch them deliberately at system boundaries — API handlers, job entry points, the outermost layer of a CLI command — where there's enough context to decide what "handling" the error actually means: retry it, log it with useful metadata, return a clean error to the caller, or fail loudly on purpose.

// Scattered defensive handling — every layer guesses independently
async function getUserOrders(userId) {
  try {
    const user = await db.getUser(userId);
    try {
      const orders = await db.getOrders(user.id);
      return orders;
    } catch (e) {
      console.log('orders failed');
      return [];
    }
  } catch (e) {
    console.log('user failed');
    return null;
  }
}

// Errors propagate; one boundary decides what handling means
async function getUserOrders(userId) {
  const user = await db.getUser(userId);
  return db.getOrders(user.id);
}

// The API route — the actual boundary — decides what "handled" means
app.get('/users/:id/orders', async (req, res) => {
  try {
    const orders = await getUserOrders(req.params.id);
    res.json(orders);
  } catch (error) {
    logger.error('Failed to fetch orders', { userId: req.params.id, error });
    res.status(500).json({ error: 'Unable to retrieve orders' });
  }
});

The internal function stays simple and readable. The boundary layer — which actually knows what the caller needs — makes the real decision about retries, fallbacks, and user-facing messaging.


3. They Delete Code as Confidently as They Write It

Junior engineers tend to accumulate: an extra parameter "in case we need it later," a commented-out block instead of a deletion, a feature flag nobody's checked in eight months, an abstraction built for a second use case that never arrived.

Senior engineers treat unused code as a liability, not a convenience. Dead code isn't free — it's a maintenance cost, a source of confusion for new team members, and a place bugs hide because nobody's looking at it. The habit that separates experienced engineers isn't writing better code on the first pass. It's a willingness to delete their own code — and other people's — without ceremony, trusting version control to hold the history if it's ever actually needed again.

This shows up concretely in code review: a senior engineer's comment is as likely to be "can we remove this" as "can we add this." Both are forms of the same skill — judging what the codebase actually needs versus what feels safe to keep.


4. They Write the Test That Would Have Caught the Last Bug

A pattern that appears again and again in strong engineering teams: after a production incident, the fix isn't considered complete until there's a test that would have caught it before it shipped. Not a test for the feature in general — a test for the specific failure mode that just happened.

This is a different mindset from "we need more test coverage" as an abstract goal. It's targeted: every incident becomes a permanent addition to the safety net, aimed exactly at the failure that already proved it was possible.

# The bug: a discount code applied twice when a user double-clicked checkout
def test_discount_not_applied_twice_on_duplicate_submission():
    """
    Regression test for INC-2847: discount codes were applied 
    multiple times when checkout was submitted more than once 
    for the same order, due to a missing idempotency check.
    """
    order = create_test_order(discount_code="SAVE20")
    apply_checkout(order)
    apply_checkout(order)  # simulates the double-click

    assert order.discount_applied_count == 1
    assert order.total == expected_total_with_single_discount

Over time, this habit compounds into a test suite that's shaped by the actual failure history of the system — not by an abstract coverage percentage, but by every real way the system has genuinely broken before.


5. They Optimize for the Diff, Not Just the Destination

When a senior engineer makes a large change, the pull request often arrives as a sequence of small, individually reviewable commits rather than one enormous diff. Rename first. Extract the function second. Change the behavior third. Each commit compiles, passes tests, and does exactly one thing.

This isn't pedantry. It's a direct response to how code review and debugging actually work. A reviewer can genuinely evaluate a 40-line commit that does one thing. Nobody can meaningfully review a 2,000-line commit that renames variables, restructures three modules, and changes business logic all at once — so review becomes theater, and bugs slip through in the noise.

The same habit pays off later during debugging: git bisect only works well when history is made of small, coherent, working commits. An engineer who structures their work this way is — often without saying it out loud — making every future debugging session faster, including their own.


6. They Ask "What Happens When This Fails?" Before "Does This Work?"

The instinctive question when writing new code is "does this do what I want on the happy path?" The habit that shows up consistently in senior engineers is asking a second question just as early: what happens when the network call times out, when the third-party API returns malformed data, when two requests hit this code at the exact same millisecond, when the disk is full?

This isn't pessimism. It's the recognition that in any system running at real scale, over enough time, every one of those "edge cases" will happen — often more than once, often at the worst possible moment. Designing for failure upfront is dramatically cheaper than retrofitting it after an incident, because retrofitting means changing an interface that other code has already come to depend on.

Concretely, this habit shows up as questions in code review: "what happens if this API call fails halfway through?" "Is this operation safe to retry?" "What happens if two users submit this at the same time?" These questions cost nothing to ask and are far more expensive to answer after the system is already in production and something has already gone wrong.


7. They Explain the "Why," Not Just the "What," in Comments and Commit Messages

Code that explains what it does is already self-evident to anyone who can read the language — that's what code is for. What's genuinely hard to reconstruct later is why a particular decision was made, especially when the obvious alternative was rejected for a non-obvious reason.

# Not useful — the code already says this
# Loop through users and check if active
for user in users:
    if user.status == 'active':
        ...

# Useful — explains a decision nobody could infer from the code alone
# We deliberately check status == 'active' rather than 
# is_active() here, because is_active() also checks trial 
# expiration, and billing reconciliation needs the raw account 
# status independent of trial state. See INC-3021.
for user in users:
    if user.status == 'active':
        ...

The same discipline shows up in commit messages and PR descriptions: not "fixed bug" or "updated function," but the actual reasoning — what broke, why it broke, why this particular fix was chosen over the alternatives that were considered and rejected. Six months later, when someone (often the same engineer) is staring at that line wondering why it isn't written the "obvious" way, that one sentence saves a full afternoon of git-blame archaeology and Slack-thread reconstruction.


8. They Read the Error Message Before They Read the Stack Trace

The instinctive move when something breaks is to scroll straight to the bottom of a stack trace, find the line number in your own code, and start changing things near it. Senior engineers tend to do something slower first: read the actual error message, word for word, and ask what it's literally claiming happened.

A surprising number of production bugs are solved faster by taking the error message at face value than by jumping straight to hypothesis-driven debugging. "Connection reset by peer" is not the same problem as "connection refused," and treating them as interchangeable — because both show up as a failed request in the logs — sends you down the wrong path for twenty minutes before you circle back and actually read what it said.

This habit extends to reading documentation and library source before assuming behavior. It's often faster to spend three minutes confirming what a function actually does than to spend thirty minutes debugging code built on an assumption about it that turns out to be wrong.

# The actual error, read literally:
ConnectionResetError: [Errno 104] Connection reset by peer

# Wrong instinct: jump to the stack trace, assume it's a timeout,
# and start adding retry logic with a longer timeout value.
# (This does nothing — a reset is not a timeout.)

# Right instinct: read what it says. "Reset by peer" means the
# OTHER side actively closed the connection mid-request — often
# a load balancer idle-timeout, or the upstream service crashing
# mid-response. Check the upstream service's logs and the load
# balancer's idle timeout setting FIRST, before touching your
# own retry logic at all.

Twenty minutes were nearly lost tuning a timeout value that was never the problem — because the error message was skimmed instead of read.


9. They Make the Blast Radius of a Change Visible Before They Ship It

Before merging something that touches shared code — a utility function, a shared component, a database migration — senior engineers habitually ask a very concrete question: what else calls this, and what happens to each of those callers if this changes?

This sounds obvious, but it's the step that gets skipped most often under deadline pressure, and it's the direct cause of a large share of "how did this break something completely unrelated" incidents. A one-line change to a shared function's default behavior can silently alter output for a dozen other features that depend on it — and nobody notices until one of them breaks in production, disconnected in time and context from the change that caused it.

The habit isn't paranoia — it's a five-minute grep or IDE "find usages" pass before touching anything shared, plus a genuine read of what each caller expects. Engineers who skip this step aren't being reckless on purpose; the step is just invisible until you've been burned by skipping it once.

// A "small" change to a shared utility
// Before:
function formatPrice(amount) {
  return `$${amount.toFixed(2)}`;
}

// After — looks like a harmless improvement:
function formatPrice(amount, currency = 'USD') {
  return new Intl.NumberFormat('en-US', {
    style: 'currency',
    currency,
  }).format(amount);
}

// A five-minute "find usages" before shipping reveals:
// - checkoutSummary.js expects the old "$12.34" format exactly,
//   for a string comparison in a snapshot test
// - invoiceEmail.js concatenates the result directly into HTML
//   and assumes no thousands separator — new format adds one
// - analyticsLogger.js parses the output back into a float by
//   stripping the "$" — Intl.NumberFormat's spacing breaks it
//
// None of these callers are "wrong." The change just silently
// altered a contract three other features depended on.

Nobody notices the checkout snapshot test failure or the invoice formatting bug until days later — disconnected in time from the one-line change that caused both.


10. They Timebox Investigation Before Escalating or Asking for Help

There's a real skill in knowing how long to struggle with a problem alone before bringing someone else in — and senior engineers tend to be explicit about it, even just to themselves: "I'll spend 30 minutes on this, and if I'm not converging, I'll ask."

This cuts against two failure modes at once. The first is asking for help too early, before doing the basic legwork — reading the error, checking recent changes, searching the codebase — which wastes other people's time and doesn't build the debugging skill that comes from sitting with a hard problem. The second, more common failure mode among engineers trying to prove themselves, is burning half a day silently stuck, when a five-minute conversation with someone who has different context would have unblocked them immediately.

The habit isn't about the exact time limit. It's the discipline of noticing when you've stopped making progress and treating "ask for help" as a normal, planned step in debugging — not a last resort you reach only after quiet frustration has turned into wasted hours.

9:41 AM — Bug reported: payments intermittently double-charge

9:42 AM — Set a mental timer: "30 minutes, then I ask Priya,
          she owns the payments service"

9:42–9:55 — Basic legwork first: read the error logs, check 
            recent deploys, search the codebase for recent 
            changes to the charge endpoint. Nothing obvious.

9:55–10:10 — Form a hypothesis: maybe it's a retry-without-
             idempotency-key issue. Trace the retry logic. 
             Looks correct on the surface, but something's off 
             in how the queue re-delivers messages.

10:11 — 30 minutes up, no convergence. Message Priya:
        "Seeing intermittent double-charges. Traced it to the 
        retry path in charge-service, but the queue re-delivery 
        logic is unfamiliar territory for me — could use 10 min 
        of your context on how at-least-once delivery is 
        supposed to be handled here."

10:14 — Priya: "Ah — that queue doesn't dedupe by default, 
        we handle idempotency in the consumer with a Redis 
        lock. Check consumer.js line 40, that lock's probably 
        expiring too fast under load."

10:19 — Root cause found. Would have taken until at least 
        noon to arrive here alone.

The message to Priya isn't a surrender — it's a concise handoff of everything already ruled out, which is exactly what makes a five-minute conversation actually take five minutes instead of thirty.


11. They Prioritize Readability Over Cleverness

There's a specific kind of pride that comes from writing a dense one-liner that does five things at once. Senior engineers tend to have already been burned by their own cleverness enough times to actively resist it.

# Clever — technically correct, genuinely hard to parse at a glance
result = [x for x in (y.strip().lower() for y in data if y) if x not in seen and not seen.add(x)]

# Readable — does the same thing, costs nothing at runtime
def deduplicate_cleaned_entries(data):
    seen = set()
    cleaned_unique = []
    for entry in data:
        if not entry:
            continue
        normalized = entry.strip().lower()
        if normalized not in seen:
            seen.add(normalized)
            cleaned_unique.append(normalized)
    return cleaned_unique

The clever version isn't wrong. It's just optimized for the wrong thing — showing off understanding of the language, instead of making the logic legible to whoever reads it next under time pressure, including its own author in six months.


12. They Automate Everything: Builds, Tests, Deploys

A manual step in the path from code to production is a manual step someone will eventually forget, do wrong under pressure, or skip entirely at 11 p.m. before a deadline. Senior engineers treat "we do this by hand" as a temporary state, not a permanent process — CI pipelines that run tests on every push, deploy scripts instead of SSH-and-copy, linting and formatting enforced automatically rather than requested in review.

The payoff isn't just saved time. It's that automated steps are consistent every single time, which means when something breaks, the process itself is never the variable under suspicion — only the code is.


13. They Refactor As They Go, Keeping Tech Debt Low

The instinct under deadline pressure is to bolt a new feature onto existing code exactly as-is, even when the existing structure actively fights the new requirement. Senior engineers tend to fold small refactors into the normal course of feature work — cleaning up the function they're already touching, rather than deferring it to a "tech debt sprint" that, realistically, competes with every other priority and rarely wins.

# Touching this function anyway to add a new parameter —
# a senior engineer also cleans up the obviously confusing 
# parts while already in there, rather than layering on top
def calculate_shipping(weight, dest, exp=False, intl=False, ins=0):
    # ... 40 lines of nested if/else ...

# Same change, folded into a small refactor while already 
# in the neighborhood — not a separate "cleanup" ticket
def calculate_shipping(weight, destination, *, expedited=False, 
                        international=False, insurance_value=0):
    # ... same logic, now legible, plus the new parameter ...

This habit compounds. Codebases where every touch leaves things slightly better than before rarely accumulate the kind of debt that requires a dedicated, high-risk rewrite years later.


14. They Perform Deep, Mentorship-Focused Code Reviews

A weak code review checks whether the code runs and roughly matches the ticket. A strong one — the kind senior engineers tend to give — explains why a suggestion matters, not just what to change, and treats the review as a teaching moment rather than a gate to clear.

// Weak review comment
"Use a Map here instead."

// Mentorship-focused review comment
"Consider a Map instead of an object here — with dynamic 
string keys like this, an object risks colliding with 
inherited properties like `toString` or `constructor`, which 
has caused a subtle bug in this exact pattern before (see 
INC-2210). A Map also gives you a reliable .size instead of 
manually tracking a count."

The second version costs thirty extra seconds to write. It also means the same mistake is far less likely to recur in the reviewee's next five pull requests — the review taught a principle, not just corrected an instance.


15. They Take the Time to Understand the Business Logic

It's possible to be technically excellent and still ship the wrong thing, because the code perfectly implements a misunderstanding of what the business actually needed. Senior engineers tend to ask "why does this rule exist" before implementing it, not just "what does the ticket say" — because the ticket is frequently an approximation of intent written by someone translating a business need into an engineering task, and approximations lose detail.

Concretely, this shows up as questions before writing code, not after: "does this discount apply before or after tax, and does that match how finance calculates it?" "What should happen if a customer's subscription and their region's regulations disagree?" Engineers who skip this step build correct code against an incorrect model of the problem — and the resulting bug doesn't show up in any test, because the test was written against the same wrong model.


Key Takeaways

  • Naming and structure are a form of communication with your future self. The fifteen extra seconds it takes to write a clear name or a small commit pays for itself repeatedly over the life of the code.

  • Error handling belongs at system boundaries, not scattered through every layer. Let errors propagate internally; make deliberate handling decisions where there's enough context to make them well.

  • Deleting code confidently is as valuable a skill as writing it. Unused code is a liability, not a safety net — trust version control to remember what you no longer need visible.

  • Every production incident should leave behind a permanent, targeted test. Test suites shaped by real failure history are more valuable than ones shaped by an abstract coverage target.

  • Small, coherent commits make both code review and future debugging dramatically cheaper — for the reviewer today and for whoever runs git bisect next year.

  • Designing for failure modes upfront is cheaper than retrofitting them after an incident, because retrofitting usually means changing an interface other code already depends on.

  • Comments and commit messages should capture reasoning, not restate code. The "why" is the only part of a decision that isn't already visible in the diff.

  • Reading the error message literally, before theorizing, solves bugs faster than it feels like it should. Confirming documented behavior costs minutes; debugging a wrong assumption about it costs much more.

  • Checking the blast radius of a change to shared code takes five minutes and prevents the hardest bugs to trace — the ones that show up somewhere completely disconnected from the actual change.

  • Timeboxing investigation before asking for help is a skill, not a personality trait. Treating "ask for help" as a planned step avoids both wasting others' time and silently burning hours stuck.

  • Readability beats cleverness every time someone other than the author has to read the code — which, eventually, includes the author.

  • Manual steps in build, test, and deploy pipelines are eventually skipped or done wrong under pressure. Automation removes the process itself as a source of doubt when something breaks.

  • Small refactors folded into normal feature work prevent the tech debt that later demands a risky, dedicated rewrite.

  • A code review that explains "why" teaches a principle; one that only says "what" fixes a single instance and lets the same mistake recur.

  • Correct code built against a misunderstood business rule is still wrong — asking "why does this rule exist" before writing code catches errors no test written against the same wrong model ever will.


Conclusion

None of these fifteen habits require unusual talent or years of tenure to start practicing. What they require is a shift in what you optimize for — from "does this work right now" to "will this still make sense, and still be safe, when someone else (or future you) depends on it under pressure."

That shift is really the entire difference between code that works today and code that's still trustworthy a year from now — and, just as often, the difference between a debugging session that takes twenty minutes and one that takes all afternoon. The engineers worth learning from aren't doing anything mysterious. They've just internalized that the code they write today is a message to someone dealing with a production incident at 2 a.m. six months from now — and they write accordingly, every time, without needing to be reminded.


At Kynodex, we build production AI and engineering systems designed to survive real operational pressure — not just pass a demo. If your team is looking to raise its engineering practices from "works" to "trustworthy at scale," talk to us.


Powered by Synscribe

Comments

No comments yet. Be the first to start the conversation.

Ready to build?

Turn your AI vision into a production system

We build the AI infrastructure that powers your next stage of growth.

Book a Strategy Call