A Software Engineer at Stryten Energy plays a critical role in bridging the gap between cutting-edge software solutions and advanced energy storage manufacturing. You are not just writing code; you are building the digital infrastructure that powers the production of energy systems used in transportation, motive power, and critical infrastructure. Your work directly affects operational efficiency, quality control, and the scalability of Stryten Energy's manufacturing processes.
This role is intellectually demanding because it requires a dual focus: technical proficiency in software development and an understanding of industrial or manufacturing environments. You will often collaborate with cross-functional teams to solve real-world problems, such as optimizing flow metrics, reducing waste in production, or automating reporting systems. It is an ideal position for engineers who enjoy seeing their software have a tangible, physical impact on the factory floor and the broader energy landscape.
Initial Screening Call
reportedHalf 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
In-Depth Technical Evaluation
reportedInput 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
Onsite Visit
reportedNobody in the room with you decides this. Interviewers typically write their rounds up separately, often before seeing anyone else's, and the outcome is settled later from those write-ups. A split panel gets resolved by whichever note carries specific evidence, so what you want out of each room is one concrete thing that person could write down: a bug you caught yourself, a trade-off you named, a decision you owned. The rest is arithmetic. The project you describe in a behavioural conversation is often the same system you sketched an hour earlier, and the two accounts have to agree.
What to demonstrate
- Whether the scale, team size and timeline you attach to a project hold steady when that project resurfaces in a different round
- Whether each interviewer leaves with a specific thing to cite rather than a general impression of competence
- Whether a trade-off you defended in one round survives a challenge in another, instead of being quietly swapped for the answer the new interviewer seemed to want
- Whether a question you have already answered earlier in the day gets the same answer at the same depth, without visible impatience
How to prepare
- Write a one-page sheet per project fixing the figures you will quote — request volume, data size, team size, elapsed time, what broke — and say them aloud from the sheet until they come out identical every time
- For each round on the schedule, decide in advance the one sentence you want in that person's notes, then check in a mock that you said it outright instead of leaving it to be inferred
- Have someone ask you the same project question twice, an hour apart, and diff the two answers for numbers that moved or a trade-off that reversed
PracHub editorial advice for the preparation topics above.
Retrying an irreversible external call without an owned idempotency key
Timeouts and connection resets are ambiguous by construction - the partner may have committed before the response was lost - so a generic retry policy around an HTTP client is, in this domain, a machine for buying two labels and dispatching two trucks. Relying on the partner's deduplication is not a substitute, because their window is usually short, their key is often derived from fields you may legitimately change on retry, and many older integrations have no such concept at all. The workable pattern is to generate the key yourself, persist it with an explicit unknown status before the call, and resolve ambiguity by querying the partner for that key rather than re-issuing; the sweeper that does this is the component that has to be correct, not the call site. Teams get this right for payments and then forget that a carrier tender, a warehouse work release and an EDI shipping notice have the same shape.
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.
Retrying a write that is not safe to repeat
A timeout tells you nothing about whether the server applied the write, so a blind retry of a create or a charge can duplicate it. Either make the operation idempotent, with a caller-supplied key the server deduplicates on or a conditional update, or do not retry it; and use exponential backoff with jitter so the retries of many clients do not synchronise into a second outage.
Never running a concrete value through the code
Trace one small input and one edge input by hand, index by index, out loud. Re-reading your own code catches design mistakes; walking a real value through it catches the off-by-one, the uninitialised accumulator and the loop that never advances.
Choose a category, try a prompt, then open its approach, worked solution or follow-up when you need it.
Split an order across the fewest nodes that can fill it
A fulfilment order has L lines, L up to 25. After filtering by capability, service time and stock, N candidate nodes remain; N is usually under 14 but can reach 200. Each node covers a known subset of the lines in full. Return a minimum-size set of nodes covering every line, tie-broken by total distance. Give an exact algorithm for the small case with its exact cost in operations and bytes, say precisely why enumerating node subsets stops working, and give what you ship when N is 200.
Approach
- Name it: this is minimum-cardinality set cover, which is NP-hard, so the useful move is choosing which parameter you are exponential in rather than hunting for a polynomial exact algorithm. Enumerating subsets of nodes is 2^N and correct — at N = 14 that is 16,384 subsets and finishes instantly, which is exactly why it survives review and then fails in production at N = 200, where 2^200 is about 1.6e60.
- Be exponential in L instead, because L is the parameter you bound. Index the DP by the set of lines still uncovered:
dp[rem]is the cheapest way to cover the lines inrem, withdp[0] = (0, 0)and the value a(node_count, total_distance)pair compared lexicographically. Letbbe the lowest set bit ofrem; any cover must contain some node that stocks lineb, so the transition ranges only over those nodes:dp[rem] = min over v with b in cover(v) of dp[rem & ~cover(v)] + (1, dist(v)). Worst case O(2^L * N) — at L = 25 and N = 14 that is 33,554,432 states times 14, about 4.7e8 transitions — and the lowest-bit restriction cuts the real branching factor to the nodes stocking one specific line. - Quote the memory for what you actually store, not for a count array alone. Over 2^25 masks you need three dense arrays:
uint8node count (33.5 MB),uint32total distance (134.2 MB, integer metres so the tie-break compares exactly and the array stays fixed-width), anduint8chosen node (33.5 MB, since N <= 200 fits a byte) — about 201 MB, roughly six times the 33.5 MB that a bare count array would suggest. The same layout at L = 20 is 6.3 MB, which is why this is comfortable up to about 20 lines and needs a deliberate decision at 25. - Reconstruction is free in this formulation and needs no parent-mask array: because every transition eliminates the lowest uncovered line, the predecessor of
remunder the stored choicevis exactlyrem & ~cover(v), so walkremfrom the full mask down to 0 emittingchoice[rem]. If the reachable state count is far below 2^L — coverage sets are usually highly correlated — a top-down memoized recursion over a hash map trades the 201 MB for roughly 40-60 bytes per visited state; measure the visited count first, because a hash map that ends up touching most masks is strictly worse than the dense arrays. Add a branch-and-bound cut ofceil(popcount(rem) / max_v popcount(cover(v) & rem)), which prunes long before the theoretical bound bites. - When exact is out of reach, ship greedy with a stated guarantee: repeatedly take the node covering the most currently-uncovered lines, using bitset popcounts, O(N * ceil(L/64)) word operations per pick and at most min(N, L) picks. Greedy is within H(L) <= ln L + 1 of optimal (Chvatal), and no polynomial algorithm achieves (1 - o(1)) ln L unless P = NP (Dinur and Steurer, 2014) — so effort belongs in the pre-filter and the tie-break, not in a cleverer heuristic. At L = 25 the bound is H(25), about 3.82x worst case, while the realistic gap on correlated coverage sets is zero or one node.
- Close on the objective, because fewest nodes is not cheapest. Two shipments from distant nodes routinely cost more than three from near ones, and a split also costs a second box, a second carrier pickup and a worse customer experience. If cost is the real objective it is weighted set cover — greedy by cost divided by newly-covered lines, same logarithmic bound — and if a node can cover a line only partially, the line mask is no longer sufficient state, since you must track remaining quantity per line; that is the point at which you stop hand-rolling and either restrict splits or hand an MILP to a solver with a time budget.
Follow-up
- The pre-filter is what keeps N small. What is in it, and what happens to your exact path the day someone loosens it?
- One node can cover a line only partially. Show where the bitmask formulation breaks and what state replaces it.
- You have 40 milliseconds inside a checkout call. Which of these runs there, and what runs asynchronously afterwards?
Re-sum a movement ledger and report positions that disagree
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.
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?
Fold late out-of-order status events into a monotonic leg state
Leg statuses form the chain planned < tendered < accepted < picked_up < in_transit < delivered, with exception and cancelled outside it. You ingest 300 million observation_event rows a day carrying subject_id (leg), observed_status, occurred_at, received_at, clock_offset_ms and dedupe_key; five percent arrive over an hour late and a tail arrives a day late. Write the merge that produces each leg's current status, prove the result depends only on the set of events and not their order, and state the per-event cost.
Approach
- Define the per-leg state as a tuple ordered totally:
(status_rank, occurred_at, source_priority, event_id), and definemerge(a, b)as the maximum under that order. Rank leads;occurred_atand the rest only break ties. The proof is then one line: maximum over a total order is idempotent, commutative and associative, so the state space withmergeis a join-semilattice and the fold over any permutation of the event set yields the same value. That is also why no global sort is needed — the O(n log n) disappears and the fold is O(1) per event, O(distinct legs) state. - Rank before time, deliberately.
occurred_atis device-reported and sometimes wrong; if it led the comparison, the leg's status would be handed to whichever device has the most badly skewed clock. Ranking first means a bad clock can only reorder events within one rank. Correctoccurred_atbyclock_offset_mswhere it was measured, and where the offset exceeds a threshold keep the observation but bar it from winning a tie — the event is still evidence, it just stops being authority. - Make the lattice total, because
exceptionandcancelledare not points on the chain andmaxover a partial order is undefined for incomparable pairs. Modelcancelledas an absorbing top element, and carryexceptionas a boolean flag merged by OR alongside the rank rather than as a rank of its own — otherwise an exception at rank 4 either blocksdeliveredat rank 6 or is erased by it, and both are wrong. A product of semilattices is a semilattice, so the order-insensitivity argument still holds over the tuple. - Deduplicate on
dedupe_keyeven though the merge is idempotent. Idempotence covers a byte-identical redelivery; a partner replaying a window with a freshevent_idis not identical, and the tie-break would let the replay displace the original for no reason. Dedupe on the stable key, then fold — both halves are needed, and conflating them is the usual defect. - Partition the stream by
subject_idso all events for one leg land on one partition: broker ordering guarantees are per-partition only, and while a commutative merge makes interleaving harmless, a concurrent read-modify-write on the same leg row is still a lost update. Where that cannot be guaranteed, make the write a compare-and-set on a version column, or a conditional update that only raises the state. Because the state is mergeable, shard maps combine cleanly and replaying the whole day converges to the same answer — which is what makes reprocessing safe. - Say where genuine regressions live: a delivered shipment that is refused and returns is a new leg, not a rank decrease. Monotonicity is only sound if real reversals are modelled as new objects, and keeping every superseded observation is what lets you answer the dispute later, so the fold discards nothing — it marks losers
superseded.
Worked solution 40 min
- Write the rank table and the tuple comparison explicitly, including where
cancelledand theexceptionflag sit, before any pipeline code. - Implement
merge(a, b)as a pure function and test the three algebraic laws directly:merge(a, a) == a,merge(a, b) == merge(b, a), andmerge(merge(a, b), c) == merge(a, merge(b, c)). - Build a 200-event batch across five legs including an out-of-order
picked_upafterdelivered, a duplicateddedupe_key, onecancelled, oneexception, and one event with a three-hour clock offset. - Fold the batch, then fold 50 random permutations of it and assert every run produces identical per-leg state.
- Re-feed the entire batch a second time and assert the state is unchanged, which is idempotence end to end rather than only in the merge function.
Follow-up
- An event arrives a week late for a leg already closed and invoiced. Does it change the status, the on-time metric, both, or neither?
- Two events for one leg have the same rank and the same
occurred_atfrom different sources. What decides, and is that decision stable across a replay? - How would you test order-insensitivity rather than assert it?
Model the movement ledger so a retried write is a no-op
inventory_movement is append-only: movement_id, item_id, node_id, lot_id, state, delta_qty (signed BIGINT), uom_code, reason_code, ref_type, ref_id, idempotency_key, occurred_at, recorded_at, reversed_by. inventory_position is keyed (item_id, node_id, lot_id, state) with qty, version, last_movement_id. Give the DDL constraints and the exact statements a pick confirmation runs so that a client which times out after the commit and retries changes nothing. Then say how you correct a pick recorded against the wrong lot two days ago, and why the position carries CHECK (qty >= 0) rather than clamping at zero.
Approach
- Put the guarantee in the schema, not the handler: UNIQUE (idempotency_key) on inventory_movement. A SELECT-then-INSERT cannot work, because two concurrent retries both read nothing before either commits and both then insert.
- Claim with INSERT ... ON CONFLICT (idempotency_key) DO NOTHING RETURNING movement_id, and branch on rowcount. Zero rows means the first attempt already committed, so the handler returns success without touching the position. This branch is the whole point: applying the position update on the conflict path is what makes the ledger and the shelf disagree by one pick forever.
- Keep the insert and the position update in one transaction: UPDATE inventory_position SET qty = qty + :delta, version = version + 1, last_movement_id = :id WHERE item_id = :i AND node_id = :n AND lot_id = :l AND state = :s AND version = :v, then test the rowcount and retry the transaction on zero.
- Use lot_id BIGINT NOT NULL DEFAULT 0 rather than NULL. In a default unique index NULLs are distinct from each other, so a NULL lot would let two position rows exist for the same untracked item and split the running sum in half.
- Correct the mis-keyed pick with two compensating movements, never an UPDATE or a deleted_at flag: one reversal restoring the wrong lot, one fresh pick against the right lot, both with reason_code = 'reversal' or 'pick' and ref pointing at the original movement_id. The original row keeps its values; reversed_by is a back-pointer written in the same transaction so a reader does not need a reverse scan to see that the row was undone.
- CHECK (qty >= 0) aborts the transaction and surfaces a real defect. GREATEST(qty + :delta, 0) writes a plausible number instead, so the missing receipt that caused the negative is never investigated and the first symptom is a picker at an empty location.
Worked solution 20 min
- Write the two CREATE TABLE statements including UNIQUE (idempotency_key), the composite primary key on the position, CHECK (qty >= 0) and lot_id NOT NULL DEFAULT 0.
- Write the handler as one transaction: the ON CONFLICT DO NOTHING RETURNING insert, the rowcount branch, then the versioned conditional UPDATE with its own rowcount test.
- Send the same pick twice with one idempotency key and assert one movement row and a position that moved by delta once, not twice.
- Apply the lot correction as two new movements and show that summing delta_qty per lot now matches the shelf while both the wrong and the right lot retain a legible history.
Follow-up
- Two retries of the same pick arrive concurrently on different application instances. Walk through both transactions statement by statement and say which one commits a position change.
- occurred_at from a handheld is five minutes ahead of recorded_at because the device clock drifted. Which of your queries break, and which column should each of them actually be using?
- How long do you keep idempotency_key unique for, given the table grows by tens of millions of rows a month?
Fold late, out-of-order scan events into one status per leg
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.
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 is the fundamental difference between your approach to Lean and S…
What is the fundamental difference between your approach to Lean and Six Sigma?
Approach
- Say what you would check first and why it is the highest-information step.
- Work from the requirement backwards to the design.
- Clarify what is being asked and what a complete answer contains.
Follow-up
- How would you know your answer was wrong?
- What assumption would you test first?
If I were to task you with measuring flow through a battery, what woul…
If I were to task you with measuring flow through a battery, what would be your first step?
Approach
- Say what you would check first and why it is the highest-information step.
- Work from the requirement backwards to the design.
- Clarify what is being asked and what a complete answer contains.
Follow-up
- What assumption would you test first?
- How would you know your answer was wrong?
Idempotent movement writes from handhelds that retry blindly
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.
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
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?
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.
Prepare, practise & reflect
One practical outcome each day. Spend longer where you need it.
0 / 7 done01Diagnostic, 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.
Practice prompt ↗Practice prompt ↗Worked solution ↗02Largest 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.
Practice prompt ↗Practice prompt ↗03Drill 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.
Practice prompt ↗Practice prompt ↗04Second 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.
Practice prompt ↗Practice prompt ↗Worked solution ↗05The 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.
Practice prompt ↗Practice prompt ↗06Retest 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.
Practice prompt ↗Practice prompt ↗07Full 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.
Practice prompt ↗Worked solution ↗Expand any day for tasks and deliverables. Your progress is saved on this device.
Conflict answers where you were right and everyone came round are the weakest ones. Stronger: the evidence you went and collected, what would have changed your mind, and what you did in the weeks after the call went against you. Implementing a design you argued against, properly, is a specific and checkable behaviour.
What are three of your strengths and three of your weaknesses?
What are three of your strengths and three of your weaknesses?
Approach
- Name the disagreement and how you resolved it with evidence.
- State the situation in two sentences and spend the rest on the reasoning.
- 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 did you decide not to do, and why?
What are you passionate about?
What are you passionate about?
Approach
- Pick a story where you made the decision, not one where you watched it.
- Give the blast radius: what could have broken, and what you measured.
- State the situation in two sentences and spend the rest on the reasoning.
Follow-up
- How did you know your change caused the improvement?
- What would you do differently if you ran that again?
Describe a time you had to explain a complex technical issue to a non-…
Describe a time you had to explain a complex technical issue to a non-technical stakeholder.
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.
- Close with what you would do differently, concretely.
Follow-up
- How did you know your change caused the improvement?
- What would you do differently if you ran that again?
What is your experience with Six Sigma and Lean methodology?
What is your experience with Six Sigma and Lean methodology?
Approach
- Name the disagreement and how you resolved it with evidence.
- Close with what you would do differently, concretely.
- Give the blast radius: what could have broken, and what you measured.
Follow-up
- What would you do differently if you ran that again?
- How did you know your change caused the improvement?
- 01
What are three of your strengths and three of your weaknesses?
- 02
What are you passionate about?
- 03
Describe a time you had to explain a complex technical issue to a non-technical stakeholder.
- 04
What is your experience with Six Sigma and Lean methodology?
Is this an official Stryten Energy interview guide?
No. It is PracHub's own research and practice material for the Software Engineer role at Stryten Energy. Rounds and questions reflect what candidates have reported, not a process Stryten Energy has published, and they change over time. Confirm the current format and scope with your recruiter.
PracHub interview research ↗How long does the interview process typically take?
The process is often quite fast. Many candidates experience a timeline ranging from a few days to a couple of weeks from the initial screen to an offer.
PracHub interview research ↗What is the best way to prepare for the technical panel?
Focus on the projects you have listed on your resume. Be ready to explain the architecture, the challenges you faced, and the specific impact your work had on the business.
PracHub interview research ↗Does Stryten Energy value manufacturing experience for this role?
Yes, while it is not always a mandatory requirement, having prior experience in a manufacturing environment is a significant plus as it helps you understand the operational context of Stryten Energy's work.
PracHub interview research ↗What is the culture like at Stryten Energy?
Candidates describe Stryten Energy as a collaborative, results-oriented organization. Candidates report that clear communication, honesty, and a proactive approach to solving problems are valued.
PracHub interview research ↗Sources & methodology 3 sources ↗
No official company page is cited. Rounds and questions come from candidate reports and PracHub editorial material; each source shows the date it was read.
- 01PracHub interview research ↗
PracHub editorial research into this company and role, maintained with this guide. Candidate-reported, not an employer publication.
PracHub page · Accessed 2026-09-22 - 02PracHub Software Engineer practice ↗
Cross-company practice questions for this role.
PracHub page · Accessed 2026-09-22 - 03PracHub interview preparation framework ↗
The framework the preparation plan follows.
PracHub page · Accessed 2026-09-22