Interview guides · Federal Energy Regulatory Commission
Federal Energy Regulatory Commission Software Engineer Interview Questions & Guide 2026
Federal Energy Regulatory Commission software engineer prep: algorithm drills where every complexity claim has to name the assumption under it.
The 60-second brief
The role of a Software Engineer at the Federal Energy Regulatory Commission (FERC) is crucial in developing and maintaining the software systems that enhance the agency's capabilities in regulating the electricity, natural gas, and oil industries. As a Software Engineer, you will contribute to the development of applications that support critical functions, including data analysis, compliance monitoring, and regulatory reporting. Your work directly impacts how effectively FERC can carry out its mission to ensure just and reasonable rates, eliminate undue preferences, and foster competition within its jurisdiction.
Scope in the operational half of the job when the seat carries on-call. Rollout, rollback and what you would put on a dashboard belong inside a design answer rather than after it, and an answer that never reaches them reads as someone who has built systems but not run them.
Focus areas
- Reserve inventory without overselling under concurrent allocation
- Make every external side effect safe to retry
- Reconcile a movement ledger against derived positions
Overview
This position presents an exciting opportunity to engage with complex technical challenges while serving the public interest. You will be part of a team that works on innovative solutions to improve efficiency and transparency in regulatory processes. Projects may involve developing tools for data visualization, creating APIs for data exchange, or implementing security measures to protect sensitive information. The collaborative environment emphasizes leveraging modern technologies and methodologies to drive impactful change.
Candidates can expect to work on high-stakes projects that require a blend of technical acumen and a commitment to public service. You will not only solve immediate technical problems but also contribute to shaping the future of energy regulation in the United States.
The Federal Energy Regulatory Commission interview process
Federal Energy Regulatory Commission candidates report 3 rounds · ≈ 3-5 weeks. The stages below are what candidates describe, not a published process.
Initial Screening Call
Half of this call is the part candidates treat as small talk: start date, notice period, work authorisation and its timing, location and time zone, on-call, and the number. Those are what kill offers late, after several engineers have each spent a day. Surfacing a hard constraint now costs you nothing and occasionally buys you something, since a loop compressed to fit a competing deadline can usually only be arranged if it is asked for early. The common failure is deflecting the compensation question twice, then discovering at offer stage that the band never reached your number.
What to demonstrate
- Whether your hard constraints are compatible with the role before a loop gets booked: earliest start, notice period, what authorisation you hold and when it needs action, days on site, willingness to carry a pager
- Whether you give a compensation range with something behind it, such as current total compensation or a competing timeline, rather than leaving the band untested
- Whether your stated timeline is real, since a competing deadline raised now is something scheduling can sometimes work around and the same deadline raised at offer stage usually is not
How to prepare
- Write each constraint down in one line before the call and state them as facts rather than negotiating them live under a question you were not expecting
- Set your range from two or three current data points for that level and location, and name the structure you are quoting in, so the number is comparable to the one they are holding
- If another process is running, say where it stands and by when, and ask directly whether this loop can be scheduled inside that window
Technical Interviews
Input bounds are the part of the prompt most often skimmed, and they usually contain the answer. They tell you which complexity class is admissible, which narrows the search before you have thought about the problem itself. As a rough planning figure, a compiled language does on the order of 10^8 simple operations per second and an interpreted one roughly an order of magnitude less. So n up to about twenty admits enumerating subsets, a few thousand admits a quadratic pass, and a million admits neither: you need near-linear, or linear with a log factor. If the bounds are missing, ask for them.
What to demonstrate
- Whether the approach is justified by the stated input size rather than by whichever pattern you recognised first
- Whether you ask about the properties that change the algorithm: whether the input arrives sorted, whether duplicates occur, whether values are bounded integers, whether it all fits in memory
- Whether you can name the bottleneck in your own solution and what would remove it, even when you deliberately leave it in place
- Whether a claimed speedup is real, since memoising a recursion only helps when subproblems genuinely overlap and the state can be keyed cheaply
How to prepare
- For each algorithm you rely on, write down the largest n it handles in roughly a second, then check two of those figures by timing them in the language you will actually type in
- For two weeks, write one line naming your target complexity and the bound that justifies it before you write any code, then compare that line with what you ended up submitting
- Practise the conversion backwards: given a required O(n log n), list the mechanisms that get you there (sorting, a heap, an ordered map, divide and conquer) and choose by what the problem needs to query, not by what you used last
Final Interview
Where the day includes a partner from product, design or data, that conversation is weighted like the technical ones and prepared for least. They are deciding one thing: whether having you in the room makes their decisions cheaper. That means options with costs attached, not implementation detail and not "it depends". An estimate someone can plan against — a range, the assumption that would push it to the high end, and what you would drop to hit the low one — is worth more than a confident single number, which everyone present already knows is wrong.
What to demonstrate
- Whether an estimate comes as a range with the assumption most likely to break it, and states what a specific scope cut would actually buy
- Whether a technical constraint is handed over as a choice with consequences on their side, rather than as a verdict they have no standing to argue with
- Whether you establish what decision is on the table before proposing anything
- Whether risk is raised while it can still change the plan, with the trigger that would confirm it, instead of reported afterwards as a slip
How to prepare
- Take a project that shipped late and write the two-sentence warning you could have given three weeks earlier, naming what you would have needed decided at that point
- Rehearse one estimate out loud until it arrives in three parts: the range, the single assumption that would blow it, and the smallest thing you would cut to protect the date
- Rewrite an objection you have actually made — the "we can't do that" version — as two options with their costs, so the choice ends up with the person who owns it
Where candidates lose points
Treating the database as the truth about physical stock
Every system in this domain drifts from reality, through miscounts, unrecorded damage, mis-scans and theft, so a design with no adjustment path forces the drift somewhere worse. The usual shape is a CHECK constraint or application guard that clamps a position at zero: the negative that would have exposed a missing receipt is silently swallowed, and the first visible symptom is a picker standing at an empty location for an order the system promised. The correct posture is to let the invariant fail loudly - record the cycle-count adjustment as a first-class movement with its own reason code, surface the variance, and let planning see that this location is unreliable - because an adjustment that is indistinguishable from a normal pick destroys the only signal that would have found the root cause. A related version of the same mistake is deleting or updating a movement row to fix a mistake, which leaves the position correct and the history a lie.
Judging delivery windows and cut-offs in UTC
A delivery appointment, a receiving cut-off and a same-day promise are all statements about local wall-clock time at a specific stop, so evaluating them in UTC misclassifies on-time performance for any leg that crosses a zone, and misses cut-offs by an hour on the two days a year a region changes offset. The subtlety that catches experienced engineers is that converting a future local time to a UTC instant at write time bakes in the offset rules as they were known then: when a jurisdiction changes its rules, an appointment stored that way silently moves, which is why a future window is better stored as local wall time plus an IANA zone identifier and resolved at read time, while a past event is correctly stored as an instant. Duration arithmetic needs the same care, because a transit that spans a DST boundary is genuinely 23 or 25 hours long in local terms even though the elapsed instant count is unchanged. Storing only a date with no zone - common on partner feeds - is the version of this that cannot be fixed downstream, so the zone has to be captured at ingestion.
Listing technologies instead of trade-offs
Name the property the design needs first, such as ordered range scans, multi-entity transactions, cheap appends, or a predictable p99, then pick something that provides it and say what it gives up in exchange. Almost any component is defensible once you state the requirement it satisfies and the one it sacrifices.
Reading the constraints as preamble rather than as part of the problem
The bounds are usually there to eliminate the obvious approach: n up to 10^5 makes an O(n^2) scan roughly 10^10 operations, far outside any per-test time budget, and an input larger than memory rules out loading it at all. When a bound is not given, ask for it, then say out loud which approach it kills.
Questions & practice
Explain the time complexity of your solution and why it is optimal.
Medium · data structures and algorithms
Approach
- Walk one small example through your approach before writing the whole thing.
- Restate the input: its shape, its size, and what is guaranteed about it.
- State the target complexity and say which constraint rules the naive version out.
Follow-up
- Which test case would catch an off-by-one here?
- What is the worst case, and how likely is it on real data?
Write a function to reverse a string.
Medium · data structures and algorithms
Approach
- Restate the input: its shape, its size, and what is guaranteed about it.
- State the target complexity and say which constraint rules the naive version out.
- Name the brute-force solution and its complexity before improving on it.
Follow-up
- What is the worst case, and how likely is it on real data?
- How does this change if the input no longer fits in memory?
Solve a problem related to data structures, like trees or graphs.
Medium · data structures and algorithms
Approach
- Walk one small example through your approach before writing the whole thing.
- Choose the data structure from the access pattern, not from familiarity.
- State the target complexity and say which constraint rules the naive version out.
Follow-up
- Which test case would catch an off-by-one here?
- How does this change if the input no longer fits in memory?
Re-sum a movement ledger and report positions that disagree
Easy · aggregation · reconciliation · append-only ledger
inventory_movement holds 3 billion immutable rows: item_id, node_id, lot_id (NULL when the item is lot-untracked), state, delta_qty (signed int64 in the item's smallest transacting unit), uom_code, and reversed_by. inventory_position holds one row per (item_id, node_id, lot_id, state) with qty, version and last_movement_id, using lot_id = 0 as the sentinel for untracked. Up to 80 million distinct keys. Report every key whose summed movements disagree with the stored qty, while writes continue. State your time and space bounds and how you bound the comparison.
Approach
- Fix the key first.
inventory_movement.lot_idis NULL for untracked items andinventory_position.lot_idis 0, so the group key is(item_id, node_id, COALESCE(lot_id, 0), state). Getting this wrong does not error — it silently produces two groups that each look like a variance, and the report becomes noise nobody reads. - Cut the ledger at a watermark, but do not take
W = max(movement_id)at the start of the scan. A sequence hands out an id before the inserting transaction commits, so at the instant you read that maximum there are ids below it still in flight and invisible to your snapshot. Summingmovement_id <= Wmisses them on this run, and advancingreconciled_through_movement_idtoWmakes the miss permanent: every later incremental run starts above those ids and they are never summed again. That is a hole in the ledger's own re-derivation, not a transient skew. - Take a watermark that is provably settled instead. Bound write transactions with a statement or transaction timeout so 'the longest write' is a number T, record
(observed_at, max_movement_id)samples periodically, and use asW_safethe largest sampled id whoseobserved_atis older than T — every id at or below it has committed or rolled back. Sum rows withmovement_id <= W_safe, compare only against position rows withlast_movement_id <= W_safe, treat anything newer as a write that raced you rather than a variance, and advancereconciled_through_movement_idonly toW_safeso the next run starts there instead of re-reading 3 billion rows. If the ledger carries a commit timestamp, cutting on that is the same guarantee without the sampling table. - Sum
delta_qtyas int64, and include reversal rows.reversed_byis a back-pointer for audit, not an exclusion filter: an original of +10 and its reversal of -10 must both be summed to reach 0. Excluding the original while keeping the reversal produces -10 and a false variance on every corrected key. - Guard the denomination rather than trusting it.
uom_codeis stored per row because pack factors change over time, so reject — do not sum — any row whoseuom_codeis not the item's smallest transacting unit, and report those keys separately. A mixed-denomination sum is arithmetically meaningless and looks exactly like a real variance. - Hash aggregation is O(n) time and O(distinct keys) space: 80 million entries at roughly 40-56 bytes each in a typical runtime is 3-5 GB, so quote the number. The bounded-memory alternative is an external sort-merge on the group key — O(n log n) comparisons, resident memory bounded by the merge fan-in rather than by key count, and it streams — or hash-partition by
hash(item_id) % Pand run P independent passes for 1/P of the peak.
Worked solution (25 min)
- Write the group key expression down explicitly, including the NULL-to-0 mapping, before any aggregation code.
- Derive
W_safefrom a sample older than the transaction-duration bound T, and state in one sentence which rows on each side are in scope — this is the part that makes the report trustworthy under live writes, and the part that decides whether an in-flight row is re-read next run or lost forever. - Implement the hash aggregation with an int64 accumulator and a
uom_codeassertion per row, emitting rejected rows to a second output rather than into the sum. - Build a fixture with four keys: one clean, one with a movement missing from the position, one with an original plus its reversal, one with a row in cases where the item transacts in eaches.
- Run it, then re-run with the fixture rows shuffled, with a concurrent writer appending past the watermark, and with a writer that reserved an id below
max(movement_id)and commits only after the scan finishes — then run the incremental pass and confirm that row is summed there.
Expected result: Exactly one key is reported as a quantity variance; the reversed key reconciles to zero difference; the mixed-unit key appears in the rejected-rows output rather than the variance list; rows appended past the watermark change nothing; the row whose id was reserved below max(movement_id) but committed late still sits above W_safe, so the next incremental run sums it instead of skipping it permanently.
Verify it yourself
- Summing the full ledger for a clean key equals its
inventory_position.qtyexactly, with no tolerance — a tolerance here would hide the defect the job exists to find. - Two runs at the same
W_safeover the same data produce byte-identical output regardless of row order. - Setting the watermark to
max(movement_id)instead ofW_safemakes the late-committing fixture row vanish from this run and from every run after it — the failing test that shows the sampled watermark is load-bearing. - Deleting the
reversed_byfilter (if you wrote one) changes the result, which tells you the filter was load-bearing and wrong.
Follow-up
- A key shows a variance of exactly one pick, repeatedly, at one node. What do you look at first, and what would distinguish a duplicate movement from a missed one?
- The job takes six hours and the variance report is stale by the time anyone reads it. How would you make it incremental without losing the guarantee that it re-derives from the ledger?
- Who writes the correcting movement, and what reason code does it carry?
Stop two allocators promising the same unit of stock
Medium · isolation levels · lock contention · compare-and-set
inventory_position is keyed (item_id, node_id, lot_id, state) and holds qty BIGINT, which is on-hand stock and is moved only by the movement ledger; available_qty BIGINT, maintained on the same row; and version. The declared invariant is available_qty = qty - SUM(allocation.qty) over that key in status held or committed, enforced by CHECK (available_qty >= 0) and CHECK (available_qty <= qty). allocation holds allocation_id, order_line_id, item_id, node_id, lot_id, qty, tier, status (held, committed, consumed, released, expired) and expires_at. Twelve allocator threads try to claim the last eight units of one item at one node inside the same 50 ms. Give the exact statements that let at most eight units be claimed, then say what changes if identical code runs under PostgreSQL REPEATABLE READ versus InnoDB REPEATABLE READ, and what a retried allocate for the same order line must do.
Approach
- Collapse the check and the write into one statement whose WHERE clause is the business rule: UPDATE inventory_position SET available_qty = available_qty - :q, version = version + 1 WHERE item_id = :i AND node_id = :n AND lot_id = :l AND state = 'on_hand' AND available_qty >= :q. qty is deliberately untouched: a hold is a promise, not a physical move, so only a movement may change on-hand and the ledger-equals-position invariant survives. Rowcount zero is a refusal, not an error, and the allocation row is inserted in the same transaction only when the rowcount is one.
- If the deployment carries no available_qty column and availability must be derived, the alternative is SELECT ... FOR UPDATE on the position row followed by the SUM over held and committed allocations inside the same transaction. That is correct but holds the row lock for two statements instead of one, which matters on a key taking hundreds of writes a second.
- Isolation is not a substitute for the atomic step, and it changes the error contract. Under PostgreSQL READ COMMITTED the losing UPDATE blocks, then re-evaluates its predicate against the newly committed row version and returns rowcount 0, which the handler already handles. Under PostgreSQL REPEATABLE READ the same statement instead raises SQLSTATE 40001 and the whole transaction must be retried, so a handler that only tests rowcount fails closed on an exception it never expected. InnoDB REPEATABLE READ does neither: a locking read or UPDATE performs a current read of the latest committed row, so the loser blocks and then sees rowcount 0, with a deadlock rather than a serialization failure as its exceptional case.
- Make the retry a no-op with a partial unique index: UNIQUE (order_line_id, node_id, lot_id) WHERE status IN ('held','committed'). Then a retried allocate conflicts instead of double-claiming, and the handler reads the existing allocation and returns it.
- Never source the availability number from a replica or a cache for this path. Replication lag is largest during the write burst that made the key hot, so the guarantee fails precisely when it is load-bearing.
- For the hottest item-node pairs, shed load without overselling: route allocations for that key through a single writer with a bounded queue and refuse with a conservative not-available when the queue is full. Splitting the position into N sub-rows cuts contention N-fold but strands the remainder across sub-rows and produces false out-of-stock when the last units are spread thin.
Follow-up
- One order line needs two units from two different nodes, atomically. What do you do when those positions live in different shards and no distributed transaction is available?
- Soft-tier allocations expire. Show the sweeper statement, and say what stops it from releasing an allocation that is being committed at that instant.
- Under sustained contention your CAS retries. What is the retry budget, and what does the caller see when it is exhausted?
Fold late, out-of-order scan events into one status per leg
Medium · window functions · event time · monotonic fold
observation_event holds event_id, subject_type, subject_id, observed_status, occurred_at, received_at, dedupe_key (UNIQUE) and processing_status. A lookup table maps observed_status to status_rank SMALLINT along a progress lattice. Write one query returning the current status of every shipment leg that received an event in the last hour, such that a picked_up event arriving after a delivered event never changes the answer. Then explain exactly which part of your ordering makes a shuffled replay of the same batch produce identical output.
Approach
- Separate the two jobs. Deduplication is the UNIQUE on dedupe_key at ingest, because partner feeds resend whole windows; ordering is the fold. Either alone leaves a defect: dedupe without a lattice still lets a stale event win, and a lattice without dedupe double-counts a replayed window in anything that aggregates.
- Select the affected legs in one CTE by received_at >= now() - interval '1 hour', then fold over every event for those legs, not only the recent ones. Restricting the fold's input to the recent window is the failure: a lone late picked_up would be the only row considered and the leg would regress.
- Fold as a maximum over the lattice: SELECT DISTINCT ON (subject_id) subject_id, observed_status FROM ... ORDER BY subject_id, status_rank DESC, occurred_at DESC, event_id DESC. The equivalent form is ROW_NUMBER() OVER (PARTITION BY subject_id ORDER BY status_rank DESC, occurred_at DESC, event_id DESC) = 1.
- The determinism comes from the final event_id tiebreak. Rank and occurred_at alone leave two equal-rank events at the same instant to be resolved by heap order, which changes when rows are re-inserted, so the shuffled replay differs in exactly the cases nobody tests. With a total order over the tuple the fold is a max, which is commutative and associative and therefore order-insensitive.
- Do not advance a leg on occurred_at alone. The timestamp is device-reported and can be wrong, which is why the lattice rank leads the ORDER BY and why clock_offset_ms is worth storing: a future-dated event with a low rank then cannot promote itself past a delivered.
- Keep superseded rows and mark processing_status = 'superseded' rather than deleting them. The discarded event is usually the one that explains a delivery dispute, and deleting it makes the trace unanswerable.
Worked solution (30 min)
- Build a fixture for one leg with picked_up at T0, in_transit at T1, delivered at T2, then insert the picked_up row last so arrival order contradicts event order.
- Write the CTE selecting affected subject_ids by received_at, then the DISTINCT ON fold joined to the rank lookup.
- Run it and confirm delivered is returned, then shuffle the insert order of the same batch into a fresh table and re-run.
- Add a second delivered event at the identical occurred_at from a different source and confirm the event_id tiebreak still picks one deterministically.
Expected result: One row per leg carrying the highest-ranked status ever observed, identical across every insert ordering of the same batch, with the late picked_up present in the table and marked superseded rather than absent.
Verify it yourself
- Replay the batch in five random orders and diff the result sets; they must be byte-identical.
- Remove the event_id tiebreak and confirm the equal-timestamp case becomes unstable across replays.
- Restrict the fold input to received_at in the last hour and confirm the leg regresses to picked_up, reproducing the trap.
- Confirm every input event is still present with a processing_status, so no evidence was discarded.
Follow-up
- An event arrives a week late for a leg that is already invoiced. Does the fold apply it, reject it as late, or apply it and emit a correction?
- The stream is partitioned by source_id rather than by subject_id. What ordering guarantee do you actually have, and what breaks?
- How would you materialise this fold so the customer-facing status read is not running a window function per request?
What considerations do you take into account when designing APIs?
Medium · system design and architecture
Approach
- Fix the scope first: who calls this, how often, and what they do when it fails.
- Name the failure you are designing for, then the recovery path.
- Choose a partition key and say what query it makes expensive.
Follow-up
- What breaks first when traffic grows ten times?
- How does this behave when that dependency is down for an hour?
How would you design a system for real-time data processing?
Medium · system design and architecture
Approach
- State the consistency you need, and where you are willing to be stale.
- Fix the scope first: who calls this, how often, and what they do when it fails.
- Name the read and write paths separately; they rarely have the same bottleneck.
Follow-up
- What would you drop to keep the system up under load?
- How does this behave when that dependency is down for an hour?
If you encountered a significant bug during a critical deployment, how would you handle it?
Medium
Approach
- Work from the requirement backwards to the design.
- Say what you would check first and why it is the highest-information step.
- State your assumptions explicitly before working the problem.
Follow-up
- What assumption would you test first?
- How would you know your answer was wrong?
Idempotent movement writes from handhelds that retry blindly
Easy · idempotency · append-only ledger · transactions
Handhelds post movements to the Availability Service at 2,000 per second across sites and retry on any timeout without knowing whether the first attempt committed. Each request carries item_id, node_id, state, a signed delta_qty with its uom_code, reason_code, a caller-supplied idempotency_key and a device occurred_at. Design the write: the transaction boundary across inventory_movement and inventory_position, what a duplicate returns, and what happens when the resulting position would go negative. Do not assume occurred_at precedes recorded_at.
Approach
- One local transaction. INSERT into inventory_movement with ON CONFLICT (idempotency_key) DO NOTHING RETURNING movement_id; if zero rows come back the write already happened, so read the existing row by that key and return its original outcome. The unique index is the idempotency mechanism, which means no cache, no request log and no in-memory set has to be correct for this to hold.
- Apply the delta to inventory_position in the same transaction: UPDATE ... SET qty = qty + ?, version = version + 1, last_movement_id = ? WHERE (item_id, node_id, lot_id, state) = ... AND version = ?, then test the affected-row count and re-read plus retry a bounded number of times on zero. Same transaction is exactly what stops the ledger and its derived aggregate from diverging under a crash.
- Decide the negative case explicitly. Let CHECK (qty >= 0) abort the transaction, return a typed error and raise an exception task. Clamping at zero hides the missing receipt, and the first visible symptom then becomes a picker standing at an empty location for a promise the system already made.
- Handle the in-flight duplicate, not just the settled one: a retry arriving before the first attempt commits blocks on the unique index and resolves to the conflict branch once the first commits, so the second caller's latency is bounded below by the first's. Set the device timeout above that, or the retry storm is self-inflicted.
- Keep occurred_at as reported and recorded_at as server time, store clock_offset_ms beside them, and never derive ordering from occurred_at alone. A device whose clock runs ahead produces occurred_at > recorded_at, which is a row to flag for the fold and the audit, not a request to reject.
Worked solution (20 min)
- Write the insert with its conflict clause and the read-back branch, and state precisely what the duplicate response body is.
- Write the position compare-and-set with its version predicate and rowcount test, and define the bounded retry.
- Force the negative case on paper: on_hand of 3, a pick of 5, and say what is written, what is rolled back, and who is told.
- Show the correction path for a wrong movement: a new reversing row with reason_code reversal and reversed_by set, never an UPDATE or DELETE.
Expected result: A single transaction whose idempotency comes from the unique index on idempotency_key: a duplicate reads back the first result and returns it as success, the position is updated by compare-and-set on version inside that same transaction, a would-be negative aborts and raises an exception rather than clamping, and corrections are reversing rows so inventory_movement stays append-only.
Verify it yourself
- Replaying the identical request 100 times leaves exactly one movement row and one position increment.
- For any key, the position equals the sum of that key's delta_qty values in inventory_movement.
- No code path updates or deletes an existing movement row.
- A duplicate returns the original outcome rather than a conflict error the handheld would show the picker as a failed scan.
Follow-up
- A device buffers 4,000 movements offline and posts them in one burst. What changes: batch size, ordering, and whether the idempotency key is per row or per batch?
- Write the reconciliation query that re-sums inventory_movement per key against inventory_position, and say what it must bound the sum by so a busy row does not report a false variance.
- A supervisor says yesterday's delta was wrong by two cases. What exactly gets written?
Allocation p99 hits nine seconds while database CPU stays flat
Medium · lock contention · connection pool · outbox
At 14:10 allocation p99 went from 30 ms to 9 s and an unrelated read service sharing the connection pool began failing. Database CPU and mean statement time are unchanged. pg_stat_activity shows 80 of 100 connections in state 'idle in transaction' with a mean transaction age of 18 s, and lock waits concentrated on about 30 inventory_position rows. In the same minute a carrier's rate-quote p99 went from 180 ms to 22 s. Give the ordered diagnosis, the immediate mitigation, and the structural fix.
Approach
- Ask whether the database is working or waiting, because the two look the same from the client. Flat CPU and flat mean statement time with most connections in 'idle in transaction' says the database is idle inside open transactions: the application has begun a transaction and gone off to do something else. That state name is the whole diagnosis and it points at application structure, not at the database.
- Tie the holders to the dependency rather than assuming the link. Compare the distribution of now() minus xact_start against the carrier's latency histogram in the same window; when the two track each other, the transaction is wrapping the external call. The 18 s mean transaction age against a 22 s partner p99 is that match.
- Locate the contention: join pg_locks to pg_stat_activity, group by the locked tuple, and confirm the waits concentrate on a few dozen hot (item_id, node_id) keys. Demand concentrates on those rows precisely during a promotion, which is also when partner latency degrades, so the two curves multiply rather than add.
- Mitigate without a deploy where possible. Set a call timeout on the partner shorter than the lock budget, set idle_in_transaction_session_timeout and lock_timeout so a stall sheds instead of queueing, and give the carrier gateway its own connection pool so one degraded partner cannot drain the pool an unrelated service depends on. A circuit breaker on that partner turns a 22 s wait into an immediate, handled failure.
- Fix the structure: take the external call out of the transaction. Write the allocation state change and an outbox row in one short local transaction, let a relay make the rating or tender call asynchronously with a persisted idempotency key, and advance the saga on the result. Lock hold time then equals local work, single-digit milliseconds, and is independent of any partner.
- Accept and handle what the outbox costs you. Publication becomes at-least-once, so the consumer and the external effect must both be idempotent, and the caller now sees an asynchronous outcome it must be designed for. That is the trade: bounded lock hold time in exchange for eventual consistency and duplicate delivery you have to absorb.
Follow-up
- Your mitigation adds a lock_timeout. What does the allocation path do when it fires, and what does the customer see?
- Even with short transactions, 30 rows absorb most writes on promotion day. How do you keep those keys from serialising everything?
- How do you test this before the next promotion without a degraded partner to hand?
Suggested practice checklist
Day one measures instead of guessing, under a fixed rubric, and the remaining hours are allocated in proportion to the gaps before any studying begins. The allocation is deliberately not renegotiated midweek, because the area that feels worst on day three is usually the one that is moving.
Day 1 — Diagnostic, scored before you study anything
- Sit a 110-minute diagnostic in four blocks: forty-five minutes on two coding problems, twenty-five on one design prompt taken to interface and data model, twenty of short-answer fundamentals, and twenty delivering two behavioural answers aloud.
- Score each block from 0 to 3 on a fixed rubric where 3 is correct and fluent, 2 is correct but slow or prompted, 1 is partially correct and 0 is stuck, grading the artifact rather than how the attempt felt.
- Allocate days two to five in proportion to 3 minus each block's score, write the allocation down, and commit to leaving it alone.
Deliverable: A scored rubric and a fixed hour allocation for the rest of the week.
Day 2 — Largest gap: find the boundary rather than the subject
- Split the weakest area into named sub-skills and rate each separately. For coding those are restating the problem, choosing the structure, stating the invariant, turning the invariant into loop bounds, handling empty and single-element input, and accounting for complexity out loud.
- Attempt three items positioned just above where the rating drops off, and for each write the first move you failed to make.
- Re-attempt one of them from blank four hours later with nothing open.
Deliverable: A sub-skill map with the two blocking sub-skills circled.
Day 3 — Drill the blocking sub-skill by repeating the shape
- Do eight short repetitions of the same shape rather than eight different problems, so what gets practised is the pattern and not the puzzle.
- State the rule you now hold in one sentence, then test it against a case built to break it, a sliding window over an array containing negative values, or a cache-aside read path whose invalidation message is dropped.
- Have someone else read your one-sentence rule and find the precondition you left out.
Deliverable: One rule statement with its preconditions attached and one counterexample that would have caught the incomplete version.
Day 4 — Second gap, plus maintenance on the strongest area
- Run the same sub-skill decomposition on the second-largest gap in half the time.
- Spend twenty-five timed minutes on the block you scored highest, choosing the hardest item you can still finish rather than a warm-up.
- Write whether each area fails you on recall, on setup, or on execution, and set the fix accordingly: repetition for recall, a written checklist for setup, timed work for execution.
Deliverable: A second sub-skill map plus a one-line failure diagnosis for each area.
Day 5 — The gap that is not a skill
- Record one technical and one behavioural answer, then count two things in the playback: seconds before your first clarifying question, and sentences you began without knowing where they would end.
- Practise saying that you do not know, followed by how you would find out, without letting it soften into a guess, and practise stating a complexity or an estimate before being asked for it.
- Redeliver one answer under a hard ninety-second cap, which forces structure ahead of detail.
Deliverable: Two recordings with a counted reduction in time-to-first-question.
Day 6 — Retest under day-one conditions
- Sit the same 110-minute structure with new prompts of comparable difficulty and score it on the identical rubric.
- For any block that did not move, change the method rather than adding hours: a block stuck at 1 usually means the practice was too varied, not too short.
- Write down which single block you would still lose the offer on.
Deliverable: A second scored rubric placed beside the first, with one named remaining risk.
Day 7 — Full loop under interview conditions
- Run a sixty-minute mock over the two blocks that moved least, with an interviewer briefed to interrupt and change direction mid-answer.
- Write the recovery script for going blank: restate the question, state your assumption, name the first thing you would check.
- Say every rule from the week aloud without reading it, and cut any you cannot state in a single sentence, since a rule you have to reconstruct mid-answer will not survive an interruption.
Deliverable: A one-page card holding the recovery script and only the rules you could state from memory.
Outage answers are graded on instruments
Nobody is scoring your stamina at three in the morning. What carries weight is which signal told you something was wrong, what you measured before touching anything, what you rolled back versus what you fixed forward, and why you picked one. 'We restarted it and it went away' is a story about not knowing.
How do you handle conflict within a team?
Medium · behavioural and engineering judgement
Approach
- Close with what you would do differently, concretely.
- Name the disagreement and how you resolved it with evidence.
- Pick a story where you made the decision, not one where you watched it.
Follow-up
- What did you decide not to do, and why?
- What would you do differently if you ran that again?
What motivates you to work in the public sector?
Medium · behavioural and engineering judgement
Approach
- State the situation in two sentences and spend the rest on the reasoning.
- Give the blast radius: what could have broken, and what you measured.
- Pick a story where you made the decision, not one where you watched it.
Follow-up
- How did you know your change caused the improvement?
- What would you do differently if you ran that again?
Estimate offline site operation you have never built
Hard · estimation · partition tolerance · unknowns · reconciliation
A site lost its wide-area link for six hours and picking stopped, because every scan confirmation needed the central inventory service. You are asked for an estimate to keep the floor running through a partition and reconcile afterwards. You have never built store-and-forward. Deliverable: an estimate with a stated range, the three unknowns that dominate its variance, what you would build in one week to collapse them, and the part of the problem you would declare out of scope. Be explicit about what the site may and may not decide on its own while cut off.
Approach
- Decompose before you price. Buffering scans locally, replaying them on reconnect, resolving conflicts, and deciding what the site may do unilaterally are four different pieces with different risk, and an estimate that does not separate them cannot be argued with.
- Split the work by what the partition permits, because this is the decision that sizes everything else. Recording movements against work already released is safe offline: they are append-only facts with device-minted idempotency keys and a local sequence, and replay is a no-op for anything already applied. Allocating new demand is not safe offline, because a promise requires a lock on a position the site cannot see. So the honest scope is that the floor keeps executing released work and stops accepting new promises, and a strong answer says that out loud rather than estimating a system that cannot exist.
- Name the three unknowns that actually drive variance rather than listing every risk: how the handheld software buffers and whether it can mint durable keys at all, what the reconnect storm looks like when several hours of movements replay against contended positions, and how conflicts are resolved when a cycle count taken centrally during the partition contradicts the replayed picks.
- Propose a one-week spike that collapses those specifically: instrument one site for real device counts and offline duration distribution, write the replay against a copy of production movement volume to measure the reconnect burst, and take the conflict question to operations rather than solving it in code, since deciding which of two contradictory counts wins is a business rule.
- Give the estimate as a range with the assumption attached to each end, and say what moves it. A single number invites being held to it; a range whose width is explained by the three unknowns invites funding the spike that narrows it.
- Declare the out-of-scope part explicitly and defend it. Offline allocation, offline rating and offline label purchase all require an authority the site does not have during a partition, and pretending otherwise converts an availability problem into a correctness problem that surfaces days later as oversold stock.
Follow-up
- The site comes back with six hours of movements and central has already adjusted two of those positions by cycle count. Which wins, and who decided?
- How would you give the estimate differently to a director who wants a date than to the engineer who will build it?
- What would make you refuse to give a number at all, and what would you offer instead?