Filter concepts by levelShowing all levels.

Python · Senior-Level Engineering Skills

Senior-Level Engineering Skills

Python knowledge alone does not make a 5-year engineer — architecture reviews, documentation, debugging production incidents, technical debt, safe migrations and deployments, and mentoring, each grounded in a real, runnable example

  • ADRs and docsContext, Decision, Consequences — a written trail
  • Debugging incidentsa correlation ID turns chaos into one timeline
  • Safe changecanary rollouts, expand/contract migrations
  • Mentoring & trade-offsgain vs. cost, in plain language, with a recommendation

1.Architecture reviews

An architecture review evaluates a proposed system-level design — usually written up as an ADR (Architecture Decision Record: context, decision, consequences) — before any code is written, so a costly structural mistake is caught on paper instead of after months of implementation.

Key features

  • An ADR records Context (the problem/forces), Decision (what was chosen), and Consequences (what this costs, including what it makes harder)
  • Architecture review happens BEFORE implementation — its entire value is catching a structural mistake while it is still cheap to change
  • A real review always names at least one real cost, not just benefits — a decision with zero listed downsides was not reviewed critically
  • A reviewer can raise a blocking concern (must be resolved before approval) or a non-blocking one (worth discussing, does not gate the decision)
  • An ADR is a paper trail: six months later, "why did we choose this?" has a written answer instead of relying on someone's memory
An ADR, and what makes a review real

Context

the problem and forces at play — why a decision is even needed

Decision

what was chosen, written down before implementation starts

Consequences

at least one real cost named — "None" means it was not actually reviewed

blocker: concern

any reviewer can raise one — it alone gates approval, regardless of concern count

  1. Context — the problem and forces at play — why a decision is even needed
  2. Decision — what was chosen, written down before implementation starts
  3. Consequences — at least one real cost named — "None" means it was not actually reviewed
  4. blocker: concern — any reviewer can raise one — it alone gates approval, regardless of concern count

Command

python
def render_adr(title, status, context, decision, consequences):
    return (
        f"# {title}\nStatus: {status}\n\n"
        f"## Context\n{context}\n\n"
        f"## Decision\n{decision}\n\n"
        f"## Consequences\n{consequences}"
    )

An architecture review happens on an ADR (Context, Decision, Consequences) before implementation, and it is only a real review if it can name at least one real cost and can raise a blocking concern that actually blocks.

2.Technical documentation

Technical documentation writes down a function's contract (what it returns, what it raises), or a project's setup steps, so a reader does not have to reconstruct that knowledge by reading every line of implementation or asking the original author.

Key features

  • A function contract states parameters, return value, and every exception it can raise — a caller who only reads the signature still needs to know what can go wrong
  • A README's job is getting a new engineer from zero to a running system — install steps, a minimal working example, nothing assumed
  • Documentation that restates what the code already says plainly (function_doc for a self-explanatory function) adds maintenance cost with no new information
  • Documentation goes stale the moment behavior changes and the docs do not — a docstring claiming a contract the code no longer honors is worse than no docstring
  • The best documentation lives next to what it describes (a docstring beside the function) rather than in a separate wiki nobody remembers to open

Command

python
def function_doc(name, params, returns, raises):
    lines = [f"{name}({', '.join(params)})"]
    lines.append(f"    Returns: {returns}")
    if raises:
        lines.append(f"    Raises: {', '.join(raises)}")
    return "\n".join(lines)

Good documentation states a contract (what a function returns and raises) or gets a new engineer running (a README's install + quickstart) — it earns its keep by adding information the code and signature do not already make obvious.

3.Debugging production incidents

Debugging a production incident means narrowing a failure to its cause with no debugger attached — a correlation ID that ties every log line from one request together, and a structured log at each boundary (before/after a DB call, an external API call) turns "it broke somewhere" into "it broke between step 3 and step 4."

Key features

  • A correlation ID (generated at the edge, passed through every downstream call) is what makes one request's log lines findable among millions of interleaved lines from concurrent requests
  • A structured log at each boundary (before/after a DB call, before/after an external API call) narrows a failure to a specific step, not just "somewhere in this function"
  • git bisect binary-searches deploy history for the exact commit that introduced a regression — O(log n) commits checked instead of a linear crawl through history
  • Reproducing locally with production-shaped inputs (the actual failing payload, not a synthetic one) catches bugs a synthetic test case never would
  • The goal during an active incident is narrowing fast, not full understanding — a bisected commit or an isolated boundary is often enough to mitigate before every detail is known
Narrowing a failure with no debugger attached

Tag at the edge

correlation_id generated once, threaded through every downstream call

Log at each boundary

before/after a DB call, before/after an external API call

Filter by correlation_id

reconstructs one request's timeline out of millions of interleaved lines

Reproduce with the real payload

the actual failing input, not a synthetic guess

  1. Tag at the edge — correlation_id generated once, threaded through every downstream call
  2. Log at each boundary — before/after a DB call, before/after an external API call
  3. Filter by correlation_id — reconstructs one request's timeline out of millions of interleaved lines
  4. Reproduce with the real payload — the actual failing input, not a synthetic guess

Command

python
import logging, uuid

logger = logging.getLogger(__name__)

def handle_request(payload):
    correlation_id = str(uuid.uuid4())
    logger.info("request_start", extra={"correlation_id": correlation_id})
    # ... every downstream log call also includes correlation_id ...

A correlation ID threaded through every downstream call turns an unreadable pile of interleaved logs into one request's timeline; narrow fast with real production inputs rather than reasoning from the code in isolation.

4.Estimating engineering work

A three-point estimate takes an optimistic, most-likely, and pessimistic guess and combines them into one expected value — (optimistic + 4*most_likely + pessimistic) / 6 — which is far more honest about uncertainty than a single number pulled from thin air.

Key features

  • PERT weighting: (optimistic + 4*most_likely + pessimistic) / 6 — most_likely counts four times as much as either extreme
  • The pessimistic estimate should account for realistic risk (a dependency turns out to be missing, a design needs revisiting) — not a worst-case asteroid strike
  • A large gap between optimistic and pessimistic signals real uncertainty in the task itself — that gap is information, not noise to average away
  • Estimating a large, vague task produces a large, vague number — breaking it into smaller pieces first (next concept) makes every individual estimate more honest
  • An estimate is a forecast, not a commitment — re-estimating as new information arrives is expected, not a failure of the original estimate

Command

python
def three_point_estimate(optimistic, most_likely, pessimistic):
    expected = (optimistic + 4 * most_likely + pessimistic) / 6
    return round(expected, 1)

A three-point estimate — (optimistic + 4*most_likely + pessimistic) / 6 — is more honest than a single guess because it makes the estimator's uncertainty visible instead of hiding it inside one number.

5.Breaking large tasks into smaller pieces

A large task splits into subtasks that are either additive (new code that does not change existing behavior) or built behind a feature flag — both are safe to merge and ship on their own, long before the whole epic is "done."

Key features

  • A subtask is independently shippable if it is additive (new code, nothing existing changes) or gated behind a feature flag (existing behavior unchanged until the flag flips)
  • The genuinely risky step — a cutover, a flag flip, removing the old path — is usually small and should be the LAST subtask, not the first
  • Shipping subtasks continuously means each one gets real code review and can be reverted independently, unlike one giant end-of-epic merge
  • A task that cannot be split into any independently shippable piece is often a sign it needs a different design, not just smaller commits
  • Splitting also improves estimation (previous concept) — five small, well-understood pieces estimate more accurately than one large, vague one

Command

python
def is_independently_shippable(task):
    return task.get("behind_flag", False) or task.get("additive", False)

Split a large task into subtasks that are additive or flag-gated — each ships and gets reviewed on its own — and save the one genuinely risky step (the cutover) for last.

6.Identifying technical debt

Identifying technical debt starts with what is already marked (a FIXME/HACK-style comment naming a known shortcut), then goes further to spot the unmarked kind — code nobody flagged, that just quietly got harder to work in over time.

Key features

  • TODO/FIXME/HACK/XXX comments are the tracked, searchable layer of technical debt — a good first pass, not the whole picture
  • A dated TODO ("TODO(2026-06-01): ...") is more actionable than an undated one — it signals when to revisit, not just that something should be revisited eventually
  • The riskier debt is untracked: code with no comment at all that the team quietly avoids touching, works around, or over-tests out of fear
  • A rising bug rate concentrated in one module, or repeated "just copy that pattern" instead of extracting a shared abstraction, are both symptoms of debt nobody labeled
  • Not all debt is bad — deliberate, tracked debt taken to hit a real deadline is a normal engineering tradeoff; the danger is debt nobody can see or plan to repay

Command

python
import re

def has_debt_marker(line):
    return bool(re.search(r"#\s*(TODO|FIXME|HACK|XXX)", line))

Grepping for FIXME/HACK-style markers finds the tracked debt someone already flagged — that is a floor, not the total; the untracked kind (a feared module, duplicated logic, a rising bug rate) needs a different kind of looking.

7.Refactoring legacy systems

Refactoring a legacy system safely starts with a characterization test — a test that pins down what the code ACTUALLY does right now, bugs and all, rather than what it should do — so a later change has something real to check against, even when no test suite existed before.

Key features

  • A characterization test asserts what the code currently does, not what it should do — even a documented bug gets pinned down, not "fixed" as a side effect of adding the test
  • Once a characterization test exists, refactoring proceeds exactly like the tests-before-tests-after case — the test just came from observation instead of a spec
  • A "seam" is a place a legacy function can be tested or replaced without touching the rest of the system — extracting a seam is often the very first refactor, before any deeper change
  • Fixing a bug discovered via a characterization test is a SEPARATE, deliberate step from the refactor — mixing "pin down current behavior" with "also fix this bug" defeats the point of the pin
  • Legacy code with tightly coupled global state or hidden dependencies often needs to extract those dependencies (see: hidden global dependencies) before a characterization test can even be written
Making a safe refactor possible with zero prior tests

Find a seam

extract hidden global deps first, if the code has no place to insert a fake

Write a characterization test

assert exactly what it returns TODAY — bugs included, not "correct" behavior

Refactor structure only

the characterization test must stay green throughout

Fix any bug separately

its own commit, its own review, its own updated test

  1. Find a seam — extract hidden global deps first, if the code has no place to insert a fake
  2. Write a characterization test — assert exactly what it returns TODAY — bugs included, not "correct" behavior
  3. Refactor structure only — the characterization test must stay green throughout
  4. Fix any bug separately — its own commit, its own review, its own updated test

Command

python
def characterize(func, *args, **kwargs):
    """Call the existing function and pin down what it ACTUALLY returns —
    used once to write down current behavior, not to judge correctness."""
    return func(*args, **kwargs)

When no test suite exists yet, write a characterization test that pins down current behavior (bugs included) BEFORE refactoring — then refactor exactly as if that test had always existed, and fix any bug it reveals as a separate, deliberate step.

8.Migration strategies

A migration strategy like the strangler fig pattern routes each request to the new system if that piece has already been migrated, or to the legacy system otherwise — letting a large migration ship in small, continuously-shippable increments instead of one risky big-bang cutover.

Key features

  • A routing layer decides, per-request, whether a given path/feature has been migrated yet — migrated paths go to the new system, everything else still goes to legacy
  • The set of migrated paths only grows over time — each addition is a small, independently shippable and revertible step (see: breaking large tasks into smaller pieces)
  • A big-bang rewrite (freeze the old system, build the whole replacement, cut over once) carries much higher risk: months of work either all works on day one or all needs unwinding
  • The legacy system is only decommissioned after the LAST piece migrates — until then, both systems coexist and the routing layer is the seam between them
  • Migration progress is measurable — the fraction of routes/features already migrated — which makes "how close are we" an actual number instead of a guess

Command

python
def strangler_route(request_path, migrated_paths):
    return "new_service" if request_path in migrated_paths else "legacy_service"

The strangler fig pattern routes migrated paths to the new system and everything else to the legacy one — migration ships incrementally, and the legacy system retires only after the last piece has moved.

9.Database migrations

A database migration is a versioned script (Alembic-style) with an upgrade() that applies a schema change and a downgrade() that reverses it — each revision links to the one before it, so the schema's full history is a chain of small, reversible steps, not a single hand-edited state.

Key features

  • Each migration records a revision id and a down_revision (the migration it builds on) — this chain is how a tool like Alembic knows the order to apply them in
  • upgrade() applies the change; downgrade() must be its exact inverse — a migration without a working downgrade cannot be safely rolled back
  • A migration should be small and single-purpose (one column, one index) — a migration doing five unrelated things is five migrations pretending to be one
  • Migrations run in order, tracked by a version table in the database itself — the database always knows which migrations it has already applied
  • A destructive downgrade (DROP COLUMN) permanently loses any data written to that column after the upgrade — rolling back is not free even when it "works"
A revision chain — each step links to the one before
  1. 0006

    down_revision of 0007

    the migration this one builds on

  2. 0007

    upgrade(): ADD COLUMN last_login_at

    nullable — existing rows have no value yet

  3. rollback

    downgrade(): DROP COLUMN last_login_at

    the genuine inverse of upgrade()

  4. 0008

    next revision, down_revision = 0007

    chains forward — the database tracks which are applied

  1. 0006: down_revision of 0007 — the migration this one builds on
  2. 0007: upgrade(): ADD COLUMN last_login_at — nullable — existing rows have no value yet
  3. rollback: downgrade(): DROP COLUMN last_login_at — the genuine inverse of upgrade()
  4. 0008: next revision, down_revision = 0007 — chains forward — the database tracks which are applied

Command

python
def upgrade():
    return "ALTER TABLE users ADD COLUMN last_login_at TIMESTAMP NULL"

def downgrade():
    return "ALTER TABLE users DROP COLUMN last_login_at"

A migration's revision chains to down_revision, and downgrade() must genuinely reverse upgrade() — a destructive change (DROP COLUMN) needs its data backed up first, or "rolling back" only restores the shape, not what was actually lost.

10.Safe deployments

A canary deployment sends a new version a small slice of traffic first, and only widens that slice while the error rate stays healthy — a bad deploy affects a fraction of users for a short window, not everyone, and never gets the chance to reach 100% traffic.

Key features

  • A canary starts at a small traffic percentage (5-25%) and widens only while the error rate stays under a defined threshold
  • A breach at any step sends traffic back to 0% — the rollout does not "hold steady and hope it recovers"
  • Widening should be gradual (25% at a time, not 0% straight to 100%) — each step is a fresh chance to catch a problem before more users are exposed
  • Safe deployment is a PREVENTIVE practice (limit exposure before full traffic); a rollback is the REACTIVE one (undo a deploy already at full traffic)
  • An automated canary check removes the "we forgot to look at the dashboard" failure mode — the rollout can revert itself before a human notices
A canary rollout, one step at a time

Deploy at 0%

new version is live but receives no traffic yet

Widen to 25%

error rate checked against threshold before advancing further

Widen again, or revert

healthy -> +25% more; breached -> straight back to 0%

Reach 100%

only after every intermediate step stayed healthy

  1. Deploy at 0% — new version is live but receives no traffic yet
  2. Widen to 25% — error rate checked against threshold before advancing further
  3. Widen again, or revert — healthy -> +25% more; breached -> straight back to 0%
  4. Reach 100% — only after every intermediate step stayed healthy

Command

python
def route_traffic_percent(canary_healthy, canary_percent):
    return min(canary_percent + 10, 100) if canary_healthy else 0

Widen a canary gradually, re-checking error rate at every step, and revert to 0% traffic immediately on any breach — a single check followed by a jump to full traffic is not a canary deployment, just a delayed all-at-once one.

11.Mentoring junior developers

Mentoring through code review means a comment explains WHY something is a problem, not just what to change — "blocker: default args are evaluated once, so a mutable default is shared across calls" teaches something reusable, while "fix this" only fixes one line.

Key features

  • A comment prefixed "blocker:" must be resolved before merge; "nit:" is a minor, optional suggestion; "question:" invites explanation rather than demanding a change
  • A teaching comment states WHY, not just what — the underlying rule, not only the specific fix for this one line
  • Mentoring is not only correcting mistakes — a "nit:" suggestion to extract a helper function models good practice without blocking anyone's progress
  • Over-blocking every minor style preference teaches a junior developer to fear review rather than learn from it — reserve "blocker:" for things that are actually wrong
  • The same why explained in review should be something the junior developer can apply unprompted next time — that transfer is the actual measure of successful mentoring

Command

python
def review_comment(kind, text):
    prefix = {"blocking": "blocker:", "suggestion": "nit:", "question": "question:"}[kind]
    return f"{prefix} {text}"

A mentoring review comment explains WHY, not just what to fix — that is what turns a correction into a lesson the junior developer can apply again on their own, and reserve "blocker:" for things that actually need to block.

12.Communicating technical trade-offs

Communicating a trade-off means stating each real option as gain versus cost in plain language — "p99 latency 500ms to 40ms" versus "a new dependency to operate" — and ending with an explicit recommendation, not a list of options with no conclusion.

Key features

  • State each option as gain (the concrete benefit) versus cost (what it actually costs — money, complexity, an ongoing operational burden)
  • Plain language over jargon — "the page loads 12x faster" lands with a stakeholder in a way "p99 latency drops from 500ms to 40ms" alone may not
  • A trade-off summary that lists options with no recommendation pushes the decision onto someone with less context than the engineer who did the analysis
  • Cost is not only dollars — "a new dependency to operate," "harder to onboard a new engineer," and "less flexible later" are all real costs worth naming
  • A one-sided pitch (all gain, no cost) is not communicating a trade-off — it is a sales pitch, and it erodes trust the first time the hidden cost surfaces

Command

python
def summarize_tradeoff(option, gain, cost):
    return f"{option}: gain={gain}; cost={cost}"

State each real option as gain versus cost in plain language, never hide the cost of the option being pitched, and end with an explicit recommendation — the engineer with the technical context should not leave that decision to whoever has less of it.

Remember

  • An ADR (Context, Decision, Consequences) makes an architecture review real — a decision with no listed cost was not reviewed critically.
  • A correlation ID threaded through every downstream call turns an unreadable pile of interleaved logs into one request's timeline.
  • A characterization test pins down what legacy code currently does — bugs included — before refactoring it, since there is no prior test suite to prove behavior held.
  • A three-step expand/contract migration (add nullable, backfill, enforce) keeps a schema change deployable at every step, never one big-bang cutover.

Pro tip

  • A canary widens traffic gradually, re-checking error rate at every step, and reverts to 0% immediately on any breach — a single check followed by a jump to 100% is not a canary.
  • State each real option as gain versus cost in plain language, and end with an explicit recommendation — a menu with no conclusion pushes the decision onto someone with less context.
  • Grepping for TODO/FIXME finds only the tracked half of technical debt — a feared module or duplicated logic with no comment is the more dangerous, untracked half.
  • Mentoring works best as targeted questions ("what happens if this list is empty?") that build the other engineer's own debugging instinct, not a handed-over answer.

Next up

  • What a 5-Year Python Engineer Should Be Able to Explain

Sources

  • Google SRE Book — sre.google/sre-book/release-engineering/
  • Martin Fowler, Strangler Fig Application — martinfowler.com/bliki/StranglerFigApplication.html
  • Every example executed on Python 3.14.3 · checked 2026-08-25

Spotted a mistake on this page? Tell us.