cswork / libraryOpen practice workspace

Interview guides · Rystad Energy

Rystad Energy Software Engineer Interview Guide

Prepare for Rystad Energy software interviews with worked Python validation, OOP and decorator examples, plus an ingestion case and practical checks.

The 60-second brief

Rystad Energy publishes energy research and data products.

This guide is scoped to a Software Engineer candidate at Rystad Energy.

Role-specific process: confirm stages and timing with your recruiter. The checkpoints below organize preparation.

Focus areas

  • Python
  • API Design
  • Coding Tests (Algorithms/DS practice)

Overview

A data-focused software interview becomes much easier to prepare for when you can demonstrate what happens to a bad row, a repeated import and a failed function call. This guide uses an energy-asset fixture to work through those decisions in Python, then connects the code to an ingestion pipeline and an API. The exercises are original practice, not a transcript of a Rystad Energy interview. Use the Software Engineer question bank for additional coding and design practice, then test your answers against the concrete contracts below.

Practice timings and scorecards here are editorial. Confirm the team, level, language, format and allowed tools with your recruiter. Existing compiled reports do not establish a universal current sequence.

Preparation checkpoints

Editorial preparation checkpoints, not a verified current hiring sequence. Historical candidate material is retained as context; exact rounds and timelines remain unverified.

Confirm the assessment

Preparation checkpoint; confirm whether this component is in your actual assessment.

What to demonstrate

  • Ask for the actual format, language, duration, tools and quantitative component.
  • If quantitative reasoning is confirmed, rehearse it under the supplied timing; this guide does not verify a fixed question count.

How to prepare

  • Ask for the actual format, language, duration, tools and quantitative component.
  • If quantitative reasoning is confirmed, rehearse it under the supplied timing; this guide does not verify a fixed question count.

Python, validation and OOP

Preparation checkpoint; confirm whether this component is in your actual assessment.

What to demonstrate

  • Run the JSON filter against threshold equality, bad units, invalid booleans and duplicate IDs.
  • Use a fake source through the OOP contract; test decorator return values, exceptions and metadata.

How to prepare

  • Run the JSON filter against threshold equality, bad units, invalid booleans and duplicate IDs.
  • Use a fake source through the OOP contract; test decorator return values, exceptions and metadata.

Ingestion and API case

Preparation checkpoint; confirm whether this component is in your actual assessment.

What to demonstrate

  • Work the authorized three-row ingestion fixture; preserve units, source, time and parser version.
  • Replay the import and define tenant authorization, a stable cursor, errors and observation dates.

How to prepare

  • Work the authorized three-row ingestion fixture; preserve units, source, time and parser version.
  • Replay the import and define tenant authorization, a stable cursor, errors and observation dates.

Project decisions and collaboration

Preparation checkpoint; confirm whether this component is in your actual assessment.

What to demonstrate

  • Prepare a project where you changed a technical decision after new evidence.
  • Explain your contribution, a disagreement and how you left a teammate owning their work.

How to prepare

  • Prepare a project where you changed a technical decision after new evidence.
  • Explain your contribution, a disagreement and how you left a teammate owning their work.

Behavioral evidence and reflection

Preparation checkpoint; confirm whether this component is in your actual assessment.

What to demonstrate

  • Rehearse an incident with observable impact and an honest account of missing measurements.
  • State what you would change next time without inventing a metric.

How to prepare

  • Rehearse an incident with observable impact and an honest account of missing measurements.
  • State what you would change next time without inventing a metric.

Where candidates lose points

Definitions without responsibilities

Use a source boundary and show substitutability instead of adding inheritance everywhere.

Invalid data silently accepted

Separate envelope failures, invalid rows and valid rows that miss the filter.

Scraping before confirming access

Choose an official API, licensed feed or agreed fixture; preserve provenance and stop on access challenges.

Managerial preparation mapped to database trivia

Prepare ownership, collaboration, disagreement and observable outcomes for a project discussion.

Questions & practice

Separate source and filter responsibilities

Medium · Python & Machine Coding

Use AssetSource and JsonSource to return a BatchResult without coupling the consumer to a source format.

Approach

  1. Separate source parsing from filtering.
  2. Define a substitutable read interface and a frozen result record.
  3. Run a fake source through the same consumer and explain composition versus inheritance.

Worked solution (25 min)

  1. Separate source parsing from filtering.
  2. Define a substitutable read interface and a frozen result record.
  3. Run a fake source through the same consumer and explain composition versus inheritance.
import json
import math

def select_assets(raw):
    data = json.loads(raw)
    if not isinstance(data, dict) or not isinstance(data.get("assets"), list):
        raise ValueError("assets must be a list")
    selected, rejected, seen = [], [], set()
    for i, row in enumerate(data["assets"]):
        if not isinstance(row, dict):
            rejected.append((i, "row must be an object"))
            continue
        asset_id, capacity = row.get("id"), row.get("capacity")
        valid_number = (type(capacity) in (int, float)
                        and math.isfinite(capacity) and capacity >= 0)
        if not isinstance(asset_id, str) or not asset_id.strip():
            rejected.append((i, "invalid id"))
        elif row.get("unit") != "MW" or not valid_number:
            rejected.append((i, "capacity must be finite nonnegative MW"))
        elif type(row.get("active")) is not bool:
            rejected.append((i, "active must be a boolean"))
        elif row["active"] and capacity >= 100 and asset_id not in seen:
            seen.add(asset_id)
            selected.append(asset_id)
    return selected, rejected

sample = '{"assets":[{"id":"wind-1","capacity":120,"unit":"MW","active":true},' \
         '{"id":"solar-1","capacity":80,"unit":"MW","active":true},' \
         '{"id":"bad-1","capacity":true,"unit":"MW","active":true}]}'
assert select_assets(sample) == (["wind-1"], [(2, "capacity must be finite nonnegative MW")])
assert select_assets('{"assets":[]}') == ([], [])
assert select_assets('{"assets":[{"id":"x","capacity":100,"unit":"MW","active":true}]}')[0] == ["x"]

from dataclasses import dataclass
from abc import ABC, abstractmethod

@dataclass(frozen=True)
class BatchResult:
    selected: tuple
    rejected: tuple

class AssetSource(ABC):
    @abstractmethod
    def read(self):
        raise NotImplementedError

class JsonSource(AssetSource):
    def __init__(self, raw):
        self._raw = raw
    def read(self):
        selected, rejected = select_assets(self._raw)
        return BatchResult(tuple(selected), tuple(rejected))

assert JsonSource('{"assets":[]}').read() == BatchResult((), ())

Expected result: The empty source returns BatchResult((), ()).

Verify it yourself

  • JsonSource returns BatchResult; changing source implementation does not change the caller contract.
  • Frozen fields prevent reassignment but do not guarantee arbitrary nested immutability.

Follow-up

  • Which edge case would invalidate your contract?
  • What changes if the input or telemetry must be bounded?

Explain shallow copies with a nested fixture

Medium · Python & Machine Coding

How does Python manage memory, and what is the difference between deep copy and shallow copy?

Approach

  1. Trace a nested list shared by two dictionaries before comparing shallow and deep copies.
  2. State which references each owner retains and when unreachable objects can be reclaimed.
  3. Do not treat the GIL as a general application-state lock.

Follow-up

  • What happens if two callers reach this at the same time?
  • Where could this allocate more than you expect?

Validate and filter an energy-asset payload

Medium · Python & Machine Coding

Return unique active asset IDs with capacity at least 100 MW. Reject malformed JSON/envelopes at request level; return indexed reasons for invalid rows.

Approach

  1. Define the envelope and row contract before coding.
  2. Validate units, finite nonnegative numbers and exact booleans.
  3. Return indexed rejection reasons separately from filtered-out valid rows.

Worked solution (25 min)

  1. Define the envelope and row contract before coding.
  2. Validate units, finite nonnegative numbers and exact booleans.
  3. Return indexed rejection reasons separately from filtered-out valid rows.
import json
import math

def select_assets(raw):
    data = json.loads(raw)
    if not isinstance(data, dict) or not isinstance(data.get("assets"), list):
        raise ValueError("assets must be a list")
    selected, rejected, seen = [], [], set()
    for i, row in enumerate(data["assets"]):
        if not isinstance(row, dict):
            rejected.append((i, "row must be an object"))
            continue
        asset_id, capacity = row.get("id"), row.get("capacity")
        valid_number = (type(capacity) in (int, float)
                        and math.isfinite(capacity) and capacity >= 0)
        if not isinstance(asset_id, str) or not asset_id.strip():
            rejected.append((i, "invalid id"))
        elif row.get("unit") != "MW" or not valid_number:
            rejected.append((i, "capacity must be finite nonnegative MW"))
        elif type(row.get("active")) is not bool:
            rejected.append((i, "active must be a boolean"))
        elif row["active"] and capacity >= 100 and asset_id not in seen:
            seen.add(asset_id)
            selected.append(asset_id)
    return selected, rejected

sample = '{"assets":[{"id":"wind-1","capacity":120,"unit":"MW","active":true},' \
         '{"id":"solar-1","capacity":80,"unit":"MW","active":true},' \
         '{"id":"bad-1","capacity":true,"unit":"MW","active":true}]}'
assert select_assets(sample) == (["wind-1"], [(2, "capacity must be finite nonnegative MW")])
assert select_assets('{"assets":[]}') == ([], [])
assert select_assets('{"assets":[{"id":"x","capacity":100,"unit":"MW","active":true}]}')[0] == ["x"]

Expected result: The fixture selects wind-1 and rejects row 2 as invalid capacity.

Verify it yourself

  • JSON envelope is valid; missing assets fails the request.
  • Threshold 100 selects; boolean capacity is rejected; selected IDs are deduplicated.

Follow-up

  • Which edge case would invalidate your contract?
  • What changes if the input or telemetry must be bounded?

Choose threads or processes from the workload

Medium · Python & Machine Coding

Explain the difference between multi-threading and multi-processing in Python, specifically in the context of CPU-bound versus I/O-bound tasks.

Approach

  1. Separate CPU computation from waiting on network or storage.
  2. Confirm the Python runtime and whether the relevant work releases its interpreter lock.
  3. Compare process isolation and serialization overhead with thread-shared state and synchronization.

Follow-up

  • What happens if two callers reach this at the same time?
  • Where could this allocate more than you expect?

Record calls without changing the function contract

Medium · Python & Machine Coding

Instrument select_assets with a decorator that preserves its return, exception and metadata.

Approach

  1. Wrap calls with functools.wraps.
  2. Return the original result and re-raise the original exception.
  3. Discuss bounded telemetry and what happens if the sink fails.

Worked solution (25 min)

  1. Wrap calls with functools.wraps.
  2. Return the original result and re-raise the original exception.
  3. Discuss bounded telemetry and what happens if the sink fails.
import json
import math

def select_assets(raw):
    data = json.loads(raw)
    if not isinstance(data, dict) or not isinstance(data.get("assets"), list):
        raise ValueError("assets must be a list")
    selected, rejected, seen = [], [], set()
    for i, row in enumerate(data["assets"]):
        if not isinstance(row, dict):
            rejected.append((i, "row must be an object"))
            continue
        asset_id, capacity = row.get("id"), row.get("capacity")
        valid_number = (type(capacity) in (int, float)
                        and math.isfinite(capacity) and capacity >= 0)
        if not isinstance(asset_id, str) or not asset_id.strip():
            rejected.append((i, "invalid id"))
        elif row.get("unit") != "MW" or not valid_number:
            rejected.append((i, "capacity must be finite nonnegative MW"))
        elif type(row.get("active")) is not bool:
            rejected.append((i, "active must be a boolean"))
        elif row["active"] and capacity >= 100 and asset_id not in seen:
            seen.add(asset_id)
            selected.append(asset_id)
    return selected, rejected

sample = '{"assets":[{"id":"wind-1","capacity":120,"unit":"MW","active":true},' \
         '{"id":"solar-1","capacity":80,"unit":"MW","active":true},' \
         '{"id":"bad-1","capacity":true,"unit":"MW","active":true}]}'
assert select_assets(sample) == (["wind-1"], [(2, "capacity must be finite nonnegative MW")])
assert select_assets('{"assets":[]}') == ([], [])
assert select_assets('{"assets":[{"id":"x","capacity":100,"unit":"MW","active":true}]}')[0] == ["x"]

from functools import wraps

def record_calls(events):
    def decorate(fn):
        @wraps(fn)
        def wrapped(*args, **kwargs):
            try:
                result = fn(*args, **kwargs)
            except Exception:
                events.append((fn.__name__, "failed"))
                raise
            events.append((fn.__name__, "ok"))
            return result
        return wrapped
    return decorate

events = []
instrumented = record_calls(events)(select_assets)
assert instrumented('{"assets":[]}') == ([], [])
assert instrumented.__name__ == "select_assets"
try:
    instrumented('not json')
except json.JSONDecodeError:
    pass
assert events == [("select_assets", "ok"), ("select_assets", "failed")]

Expected result: The event list records one success and one JSON failure; metadata remains select_assets.

Verify it yourself

  • Return values, exceptions and function name are preserved.
  • Successful and failed calls each add one event to the fixture.

Follow-up

  • Which edge case would invalidate your contract?
  • What changes if the input or telemetry must be bounded?

Optimize a specified matrix computation

Medium · Quantitative & Analytical Reasoning

How would you optimize an algorithm that processes large matrices of energy production forecasts to minimize time complexity?

Approach

  1. Define dimensions and the mathematical operation before naming a complexity.
  2. Measure the baseline and identify redundant work, sparsity or batching opportunities.
  3. Check numerical agreement, memory use and conversion costs after optimization.

Follow-up

  • How does this change if the input no longer fits in memory?
  • What is the worst case, and how likely is it on real data?

Choose storage from constraints and queries

Medium · System & API Design

Compare SQL and NoSQL databases. In what scenario would you choose MongoDB over PostgreSQL for storing energy asset metadata?

Approach

  1. Compare required queries, constraints, transactions and schema evolution.
  2. Use a relational design when historical observations and integrity rules require joins and constraints.
  3. Choose a document store for a concrete access pattern, not because the input began as JSON.

Follow-up

  • How does the query change if that join becomes one-to-many?
  • What happens to this when the table is ten times larger?

Model historical emissions with units and provenance

Medium · System & API Design

Walk through the process of designing a database schema for tracking historical carbon emissions across thousands of companies.

Approach

  1. Set the grain to one source, company, period and observation version.
  2. Store units, provenance and revisions separately from display formatting.
  3. Specify duplicate, correction and aggregation policies before indexes.

Follow-up

  • How does the query change if that join becomes one-to-many?
  • What happens to this when the table is ten times larger?

Design a tenant-scoped energy-asset API

Medium · System & API Design

Design a RESTful API to serve real-time global energy production data. How do you handle pagination, filtering, and rate limiting?

Approach

  1. Define tenant authorization, units and observation dates before pagination.
  2. Use stable cursor ordering and specify snapshot behavior across concurrent updates.
  3. Return explicit validation, permission and availability errors; state rate-limit behavior.

Follow-up

  • What happens if the caller retries after a timeout?
  • How does a client discover it is on an old version of this contract?

Cache daily data without hiding its freshness

Medium · System & API Design

How would you design a caching layer for an API endpoint that experiences high read traffic but has daily data updates?

Approach

  1. Define freshness and whether daily updates can be published as a versioned snapshot.
  2. Key cache entries by authorization scope, filter and snapshot version.
  3. Specify invalidation, miss behavior, stale serving and measurement before optimizing.

Follow-up

  • What happens if the caller retries after a timeout?
  • How does a client discover it is on an old version of this contract?

Evolve an API without silently breaking clients

Medium · System & API Design

How do you ensure backward compatibility when introducing breaking changes to an active production API?

Approach

  1. Classify the change as additive, behavioral or genuinely breaking.
  2. Maintain an explicit old-version contract and a migration window.
  3. Use contract fixtures and observe client adoption before retiring the old behavior.

Follow-up

  • What happens if the caller retries after a timeout?
  • How does a client discover it is on an old version of this contract?

Design an authorized acquisition pipeline

Medium · Web Scraping & Ingestion Case Studies

How would you design a scalable web scraping pipeline using Scrapy to crawl financial reports from hundreds of energy company websites daily?

Approach

  1. Confirm an authorized API, feed or fixture before choosing scraping.
  2. Bound acquisition, parsing, validation and storage independently; preserve provenance.
  3. Use a natural key for replay and alerts for layout changes or unexpectedly empty batches.

Follow-up

  • What breaks first when traffic grows ten times?
  • How does this behave when that dependency is down for an hour?

Choose an authorized data-access route

Medium · Web Scraping & Ingestion Case Studies

What would you do when the supplied ingestion source requires JavaScript or blocks automated access?

Approach

  1. Confirm permission and available official API or licensed feed.
  2. Use an agreed rendering mechanism only for authorized content.
  3. Stop on access challenges; do not describe bypass or evasion.

Follow-up

  • How would you detect parser-layout drift?
  • How does provenance survive a new source format?

Validate, stage and replay observations

Medium · Web Scraping & Ingestion Case Studies

How do you structure a data pipeline to validate, clean, and store scraped data into a relational database without causing bottlenecks?

Approach

  1. Parse the supplied wind-1 120 MW, solar-1 80 MW and missing-capacity rows into staging.
  2. Quarantine invalid observations and preserve source, capture time and parser version.
  3. Upsert on source, asset and observed date; replay twice and confirm no duplicates.

Worked solution (45 min)

Import wind-1 120 MW, solar-1 80 MW and bad-1 with missing capacity from an authorized fixture. Preserve provenance, replay safely and specify a tenant-scoped asset API.

  1. 5 minutes: clarify units, source access and identity.
  2. 15 minutes: parse and stage; quarantine bad-1 without converting missing capacity to zero.
  3. 10 minutes: upsert on source_id, asset_id and observed_date; replay twice.
  4. 10 minutes: define GET /v1/assets?active=true&min_capacity_mw=100&limit=50 with stable ordering, observation date and errors.
  5. 5 minutes: review tenant access, drift alerts and snapshot behavior.

Expected result: Two valid staged rows, one quarantined observation, a repeatable natural key and an API response containing wind-1, its units and observation date. This is an acceptance trace, not an executed production/database implementation.

Verify it yourself

  • Only wind-1 matches the 100 MW filter.
  • Repeated import adds no duplicate observation.
  • Missing capacity remains distinguishable from zero.
  • Unauthorized tenant receives no other tenant rows.
  • Layout drift raises an alert rather than silently succeeding.

Follow-up

  • What happens if the caller retries after a timeout?
  • How does a client discover it is on an old version of this contract?

Detect and recover from layout drift

Medium · Web Scraping & Ingestion Case Studies

Describe a scenario where a scraper failed in production because of a target site layout change. How did you detect and resolve it?

Approach

  1. Compare expected selector/row counts and schema fields with the captured source fixture.
  2. Quarantine a suspicious empty result and retain the raw authorized capture for diagnosis.
  3. Version the parser, add the changed-layout regression fixture and replay from provenance.

Follow-up

  • What happens if the caller retries after a timeout?
  • How does a client discover it is on an old version of this contract?

Quantitative rehearsal using confirmed assessment instructions

Medium · Quantitative & Analytical Reasoning

If a quantitative component is confirmed, rehearse a supplied set of logical or numerical questions under its stated timing. The original fixed question count and duration are not independently verified.

Approach

  1. Clarify what is being asked and what a complete answer contains.
  2. State your assumptions explicitly before working the problem.
  3. Say what you would check first and why it is the highest-information step.
  4. Work from the requirement backwards to the design.

Follow-up

  • What assumption would you test first?
  • How would you know your answer was wrong?

Verify an ETL result against its source

Medium · Quantitative & Analytical Reasoning

Explain how you would validate the accuracy of a massive dataset after running it through an ETL pipeline.

Approach

  1. Check schema, units, uniqueness and referential integrity before sampling values.
  2. Reconcile counts and aggregates against source fixtures with explicit tolerances.
  3. Record provenance and review rejected rows; a successful job alone does not establish accuracy.

Follow-up

  • What assumption would you test first?
  • How would you know your answer was wrong?

Listing latency scales with page size, not with filters

Easy · n+1 · latency · query batching

The tenant listing endpoint reads resource filtered by tenant_id and status, ordered by updated_at DESC, and returns each row plus the owner's display name from app_user and the actor of that resource's latest resource_revision. p99 is 55 ms at 10 rows per page and 1.4 s at 200. Database telemetry shows 401 statements per request, each under 1 ms, and nothing in the slow-query log. Diagnose the cause and give the fix, stating the statement count per request and the p99 you expect afterwards.

Approach

  1. 401 statements for 200 rows is consistent with one driver query plus two per row. Short individual execution times and an empty slow-query log support a round-trip hypothesis; they do not conclusively rule out every plan or serialization problem. Measure each layer and vary page size.
  2. Name the two per-row statements from their normalised text: a single-row app_user lookup by user_id, and a resource_revision lookup by resource_id ordered by version DESC LIMIT 1. Confirm by dropping those two response fields and watching the statement count fall to one. That locates the calls in the serialisation layer, not the repository.
  3. Check that the arithmetic accounts for the whole gap. Measure one round trip to the replica in isolation; 400 trips at roughly 3 ms of network plus 0.2 ms of execution is about 1.3 s on top of a 55 ms baseline, which matches. If the multiplication had fallen short, the N+1 would only be part of the story and you would keep looking.
  4. Batch both lookups. Collect owner_user_ids and resource_ids from the driver query, then issue WHERE tenant_id = $1 AND user_id = ANY($2) for the users, and PostgreSQL's SELECT DISTINCT ON (resource_id) ... WHERE resource_id = ANY($2) ORDER BY resource_id, version DESC for the latest revision, which the UNIQUE (resource_id, version) index serves directly. On an engine without DISTINCT ON, use a lateral join or a row_number window. Three statements per request at any page size.
  5. After batching, verify the count and measure p50/p99 under the same load. Three statements do not guarantee the old 55 ms baseline, and missing authorization or changed response semantics invalidate the fix.

Follow-up

  • The page size is capped at 200 today. What breaks first if it is raised to 2,000, and is it still this bug?
  • How do you stop the next N+1 from reaching production, given that no individual query is slow and the endpoint's tests pass?

Suggested practice checklist

Editorial rehearsal plan. Adapt it to the confirmed team, level and assessment.

Day 1 — Confirm the role

  • Ask for the actual format, language, duration, tools and quantitative component.
  • If quantitative reasoning is confirmed, rehearse it under the supplied timing; this guide does not verify a fixed question count.

Deliverable: Written format and stack questions

Day 2 — Payload validation

  • Define the envelope and row contract before coding.
  • Validate units, finite nonnegative numbers and exact booleans.
  • Return indexed rejection reasons separately from filtered-out valid rows.

Deliverable: Runnable filter and edge cases

Day 3 — Objects and substitution

  • Separate source parsing from filtering.
  • Define a substitutable read interface and a frozen result record.
  • Run a fake source through the same consumer and explain composition versus inheritance.

Deliverable: Fake source through one caller

Day 4 — Decorator contract

  • Wrap calls with functools.wraps.
  • Return the original result and re-raise the original exception.
  • Discuss bounded telemetry and what happens if the sink fails.

Deliverable: Success, failure and metadata checks

Day 5 — Authorized ingestion

  • Work the authorized three-row ingestion fixture; preserve units, source, time and parser version.
  • Replay the import and define tenant authorization, a stable cursor, errors and observation dates.

Deliverable: Three-row staging and replay trace

Day 6 — API contract

  • Define authorization before reading data.
  • Specify ordering, snapshot behavior and concurrent updates between pages.

Deliverable: Tenant, units, cursor and error specification

Day 7 — Combined mock

  • Run the ingestion case under the editorial timing.
  • State the assumption you changed and why.

Deliverable: 45-minute case plus project reflection

Behavioural preparation

Prepare evidence of your own decisions, collaboration and learning. These are editorial prompts.

Unblock an engineer without taking the keyboard

Easy · mentoring · leases · at-least-once

A teammate has spent two days on a job handler that occasionally writes duplicate rows. They are certain the queue is delivering twice by mistake. You suspect a lease expiring under a slow handler, so the job is running concurrently with itself. Describe how you have unblocked someone in this position: what you asked before offering a hypothesis, what you showed them rather than told them, and what you left them owning. Then say what you would do if their theory turned out to be the right one.

Approach

  1. Ask before diagnosing, and ask for things answerable from data they already have: the attempt count on the job rows that produced duplicates, the handler's observed duration against its lease expiry, and whether the duplicate rows share a natural key that a unique constraint could have caught.
  2. Teach the shape rather than the answer. A lease cannot distinguish a dead worker from a slow one, so a handler that outruns its lease is running twice by design, and deploys deliver the other half by killing handlers mid-run on every rollout. Both of their candidate theories produce identical duplicate rows, which is why the evidence has to come from timings rather than from argument.
  3. Hand over a checklist they execute: a natural key on every write the handler performs so the second copy collides rather than appends, the record of intent written before any external effect, a lease heartbeat while running, and the metric that shows it working.
  4. Keep ownership with them deliberately. Pair on the first write, then step back; if you finish it yourself you have closed one ticket and left the same person stuck on the next redelivery.

Follow-up

  • How would you distinguish a genuine double-delivery from a lease expiry using only the data already stored?
  • Their handler calls an external endpoint before recording that it did. What do you tell them to change first?

Argue against a design, lose, and commit anyway

Medium · disagreement · service boundaries · decision records

Describe a design you argued against and lost. State the failure you predicted as a named mechanism, not a feeling about complexity: two services that would need one transaction, a projection with no rebuild path, a write path with no idempotency key. Say what evidence you brought, what the decision maker weighed instead, and what you did after the decision was made: what you instrumented, what you wrote down, and whether the prediction came true. Five minutes.

Approach

  1. State the prediction in falsifiable form up front: the mechanism, the condition that triggers it, and the observable outcome. A prediction that cannot be checked also cannot be credited to you later.
  2. Show the evidence you had at the time and label each piece honestly as measured, analogous, or intuition. Keeping the intuition is fine; disguising it as data is the thing that erodes your standing in the next argument.
  3. Represent the opposing case at full strength, including the constraint you did not control: a fixed date, a team boundary, or the fact that the decision was cheap to reverse and yours was not.
  4. Make disagree-and-commit concrete. Name the artefact you left behind so the prediction could be settled without you: the alert and its threshold, the counter on the dashboard, the decision note that recorded the trade-off and the condition that would revisit it.

Follow-up

  • What threshold on that alert would have proved you right, and did anyone ever look at it?
  • If the same proposal arrived tomorrow with the same deadline, would you argue it the same way?

Narrate an outage you owned from page to postmortem

Hard · incident response · blast radius · postmortems

Pick an incident you personally drove, ideally one where writes were affected rather than reads. In six to eight minutes: state the symptom as it first appeared on a dashboard, the blast radius you established before you knew the cause, the mitigation you applied and when, the mechanism you eventually proved, and the follow-up that would prevent a repeat. Bring numbers: error rate, tenants affected, minutes to mitigate, minutes to resolve. If you lack exact numbers, explain the observable outcome honestly.

Approach

  1. Open on the signal rather than the cause: which metric at which percentile moved, on which service, at what time, so the listener follows the same evidence you had rather than a conclusion you already reached.
  2. Separate mitigation from diagnosis out loud. State what you did to stop the bleeding (flag off, shed traffic, drain a lease, roll back a deploy) and say plainly that you did it before the mechanism was known, because those are two jobs with different deadlines.
  3. Establish blast radius in countable terms: how many tenants, how many writes, and crucially whether the effect was loss or only delay. An append-only revision table or a pending outbox row means the change survived and the projection was merely behind, which is a repair rather than a data-loss incident.
  4. Prove the mechanism instead of asserting it. Name the trace span that grew, the plan that flipped to a sequential scan, the lease that expired, plus one alternative you ruled out and the signal that stayed flat while you ruled it out.

Follow-up

  • What would you do differently in the first five minutes, given the same dashboard and no more information?
  • Which follow-up action did you deliberately not take, and why was dropping it the right call?