As a Software Engineer at Smartestenergy, you are joining a pivotal player in the transition to a net-zero energy future. Your work directly impacts how energy data is processed, managed, and delivered, supporting the infrastructure that powers businesses across the UK. This role is not just about writing code; it is about building reliable, scalable solutions that solve complex challenges in the fast-paced energy sector.
The environment at Smartestenergy rewards engineers who are inquisitive and capable of navigating technical complexity. You will likely contribute to critical internal systems or support the operational technology that keeps the business running. Whether you are working on backend services, system maintenance, or infrastructure support, your contributions are essential to the company’s mission of leading the energy transition through digital innovation.
Conversation with Hiring Manager
reportedThe design portion here is shorter and lower-stakes than a dedicated design round, which changes what it tests. There is rarely time to reach a full component diagram, so what gets read is your first two minutes: whether you pin down constraints, meaning request rate, data size, what must not be lost and how stale a read may be, before naming any technology. Opening with a stack list invites being steered back. Once the numbers are on the table, say what breaks first if they grow tenfold, and defend the plain option where the load does not justify more.
What to demonstrate
- Whether constraints come before components: peak request rate, data volume, what must survive a process dying, and the staleness the product can tolerate
- Whether you can name what saturates first when traffic grows by an order of magnitude, and whether that matches the design you just sketched
- Whether a cache is reasoned about on both paths, since a cold or recently flushed cache sends the full request rate to the origin, so capacity has to cover the miss case and not only the steady state
- Whether you distinguish what you have operated from what you have only read about, which usually shows up in the answer to why a particular component is there
How to prepare
- Take one system you worked on and write down the numbers you would need to defend it: requests per second at peak, rows in the largest table, the latency you were actually held to. Not having them is the common stall in this part.
- Practise the tenfold question on that system out loud, naming the first bottleneck you would hit, whether that is a single writer, connection limits, disk, or a queue that grows faster than it drains, and the smallest change that buys headroom.
- Prepare one decision where the plain option was correct: the cache you did not add or the queue you did not introduce, with the load figure that made that the right call. Being able to argue for less is rarer than being able to argue for more.
Technical Assessment
reportedThe same problem is scored by two different mechanisms depending on the format, and preparing for one does not cover the other. With a person watching, partial progress is visible and a hint is a correction you can absorb; silence is the expensive failure, because nobody can read a half-written function. With an automated grader there is no partial credit for what you were about to do, nobody to ask, and the worked examples in the prompt are the entire specification. Read them as a contract, down to whether an empty result should be an empty list or no output at all.
What to demonstrate
- In a live session, whether your commentary tracks what your hands are doing, and whether a hint redirects you or gets defended against
- In an automated one, whether you cover the cases the examples do not show, since the hidden cases are where the score moves
- Whether you manage the clock on purpose: abandoning an approach that is not converging while there is still time to write something simpler that finishes
How to prepare
- Have someone hand you a problem and feed you one deliberately wrong hint. Practise testing it against a concrete case instead of accepting or rejecting it on authority.
- Do one timed run a week in a plain browser editor with autocomplete, linting and your own snippets switched off, which is closer to what these environments give you
- For the automated format, write the harness before the solution: a main that feeds the worked examples plus an empty and a single-element case and prints expected against actual, so a wrong submission is caught by you first
Team Fit Evaluation
reportedThis round is a resourcing decision in the shape of a conversation: how large a piece of work can be handed to you with a one-line brief and no check-in for two weeks. The manager is listening for the seams in your story, the places where you settled something yourself and the places you went back for a ruling. Most candidates describe the system and skip the decisions, which reads as having been present rather than responsible. Say who wanted the work, which option you rejected and why, and where you would have stopped and escalated.
What to demonstrate
- Whether you can point at a design decision that was yours rather than the team's, and name the alternative you turned down and the constraint that killed it
- What you treat as yours to settle against what you take to someone else, and how long you sit on a blocker before raising it
- Whether the scope you claim survives a follow-up into the unglamorous part of it: the data migration, the backfill, the rollout to users who were already on the old path
- Whether you asked what problem the request was solving before building what was literally asked for, and what changed in the design once you had the answer
How to prepare
- Write out the brief for your last two projects exactly as it reached you, usually one sentence in a ticket, then list every question you had to answer yourself before code could be written. That list is most of what this round is asking for.
- For one decision in each project, write down the rejected option, what you were trading against, and the piece of evidence that would have flipped you. A tradeoff you cannot argue in reverse was not really a decision.
- Have an escalation ready: a blocker you took to your manager, what you had already tried, and the specific thing you were asking them to decide. If every example is something you handled alone, that reads as someone who does not ask.
PracHub editorial advice for the preparation topics above.
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 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.
A queue or buffer with no bound
Every producer-consumer boundary needs a capacity and a policy for reaching it: block the producer, shed load, or drop the oldest entry. Unbounded buffering converts a temporary slowdown into memory exhaustion and hides the backpressure signal that would have revealed the consumer was falling behind.
Hardcoding to the sample inputs
Solve the stated problem rather than the two examples; special-casing a literal to make a sample pass is obvious immediately and reads as either a misunderstanding or an attempt to fake progress. If you genuinely cannot generalise yet, say which part is a stub and what would replace it.
Choose a category, try a prompt, then open its approach, worked solution or follow-up when you need it.
Trace a suspect lot forward to every shipped order line
lot_edge(parent_lot_id, child_lot_id) has 400 million rows with btree indexes on both (parent_lot_id, child_lot_id) and (child_lot_id, parent_lot_id). lot_shipment(lot_id, order_line_id) maps lots to shipped lines. Given one suspect received lot, return every downstream order_line_id. The reachable subgraph can reach 2 million lots and 12 levels; the full edge table does not fit in memory. Give the traversal, its complexity in terms of the reachable subgraph, and what you do if the data contains a cycle.
Approach
- Genealogy is a directed acyclic graph, not a tree: a production run consumes several lots and one lot splits across many shipments, so a node is reached by several paths and a tree traversal revisits it combinatorially. Breadth-first from the suspect lot over the
(parent_lot_id, ...)index, with a hash set of visited lot ids, is O(V' + E') where V' and E' count only the reachable subgraph — 2 million 8-byte ids is 16 MB of payload and a few tens of MB with open-addressing overhead, which is why the visited set is affordable and the edge table's 400 million rows never enter memory. - Batch each level into one indexed read. Issue
WHERE parent_lot_id = ANY($1)with a few thousand parents per call instead of a query per node: at 12 levels and 2 million nodes, per-node round trips dominate the wall clock entirely, and the recall's deadline is a real-world response time, not a query-plan aesthetic. The batch size is the tuning knob between round-trip count and per-statement planning cost. - Keep the visited set even though the schema says DAG. Rework and repack loops — material returned into an earlier lot — do occur, and an unguarded traversal on a cyclic edge set does not terminate; a set membership test is O(1) and turns a hang into a correct answer plus a data-quality alert. Also cap the traversal by level and by node count, and fail loudly on the cap rather than returning a partial set silently.
- Join to
lot_shipmentonce at the end over every visited lot id, not over the leaves and not per node. A lot is routinely both shipped and consumed: half a lot goes out on an order line while the other half is repacked into a child lot, so it has alot_shipmentrow and outgoing edges at the same time and is not a leaf. Restricting the join to nodes with no outgoing edges drops exactly those order lines, which is under-recall — the one failure mode this trace is not allowed to have. One bulk join against an index onlot_idover the whole visited set is a single pass; per-node lookups multiply the round-trip problem by the number of visited lots. - State the semantics honestly: reachability answers which shipped lines could contain material from the suspect lot, and without propagating edge quantities it is a superset of physical containment. For a recall that is the correct direction of error — over-recall, never under-recall — and saying so is the point, because a trace that returns quickly and incompletely will be believed. The upstream question, which received lots are in a suspect shipped unit, is the same traversal over the reverse index, which is why both indexes exist.
Worked solution 30 min
- Draw a six-lot fixture containing a merge (two parents into one child), a split (one parent into three children), and one interior lot that is partly shipped on an order line and partly consumed into a child lot, then write down the expected reachable set and the expected order lines by hand.
- Implement level-batched BFS with an explicit frontier list, a visited hash set, and a per-level batch read.
- Add a cycle to the fixture and confirm the traversal terminates and flags it rather than hanging.
- Bulk-join
lot_shipmentonce over every visited lot id — the whole visited set, not the leaves — and count the queries issued, confirming it is one per level plus one. - Re-run with the frontier order reversed to confirm the output set is unchanged.
Follow-up
- A recursive CTE would express this in ten lines. What is its cost here if the index on the traversal column is missing, and how would you notice?
- You need the quantity of suspect material in each downstream line, not just reachability. What has to be on the edge, and what does that do to the traversal?
- The traversal must run while a production run is writing new edges. What does a consistent answer mean here, and what do you cut it at?
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.
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?
Find the busiest ten-minute window per reader after deduplication
One day of observation_event from fixed readers: source_id (reader), subject_id (container), occurred_at, received_at, dedupe_key. 300 million rows, partner and device replays resend whole windows, and some events arrive six hours after they occurred. For each reader, return the 10-minute event-time window containing the most distinct containers, with the window's start. Target linear work per reader after sorting. State the window semantics you chose, and what the job does with an event that lands after its window was already reported.
Approach
- Deduplicate before windowing, on
dedupe_key, and keep the two concerns separate. A replayed batch is a no-op only if the duplicate is removed first; deduplicating inside the window logic counts a resend as traffic and reports a congestion peak that never happened. Dedupe is a hash set per reader-day or a sort-unique on the key — the same sort you need anyway. - Partition by
source_idand sort each partition byoccurred_at, never byreceived_at. Arrival order is an artefact of device connectivity and partner batch cadence; a reader that went offline for six hours would otherwise show its entire backlog as one impossible burst. Sorting dominates: O(p log p) per reader of size p, O(n log n) overall. - Two pointers over the sorted partition with a hash multiset
container -> countand a separatedistinctinteger. Advance the right pointer, incrementing the count and bumpingdistinctonly on a 0-to-1 transition; advance the left pointer whileoccurred_at[left] <= occurred_at[right] - 600s, decrementing and loweringdistinctonly on a 1-to-0 transition, and erasing the key from the map so the map's size anddistinctnever diverge. Each element enters and leaves once: O(p) amortised after the sort, O(window occupancy) space. - Justify the candidate set rather than scanning every possible start. With half-open windows
[t, t+600), sliding a window right until its left edge coincides with an event can only add events and never removes one, so the maximum is attained at some event'soccurred_at; checking the p windows anchored at events is sufficient and exhaustive. State the half-open convention explicitly — an event exactly att+600belongs to the next window, and flip-flopping on that produces off-by-one disagreements between this job and whatever reads its output. - Handle lateness by choosing a policy, not by hoping. Either run over a closed event-time batch with a stated lateness allowance — the day plus six hours, so the six-hour tail is inside the batch and the day-late tail is not — or emit under a watermark and restate the affected windows when a late event lands. Both are defensible; what is not defensible is silently dropping the late event, because that is how the busiest window quietly becomes the one with the best connectivity.
Follow-up
- Two events for one container arrive from two readers a second apart. Is that congestion, a misread, or one container passing two portals?
- You need this continuously rather than as a daily batch. What changes, and where does the memory go?
- A device's clock is three hours fast. What does that do to this result, and how would you detect it from
clock_offset_ms?
Decide whether available-to-promise is derived or stored
Available to promise at one node equals inventory_position.qty in state on_hand minus the sum of allocation.qty in status held or committed for that key. The read sits inside checkout: 4,000 reads per second at peak, p99 budget 20 ms. inventory_position holds 40 million rows; during a promotion a single hot item-node pair carries about 2,000 open allocations. Decide whether to compute this per read or to maintain available_qty on the position row. Give both paths, the failure mode of each, and what the browse read may do that the allocate read may not.
Approach
- Cost the derived read in tuples, not in plans. One index probe into the position is trivial; the aggregate over allocation touches one tuple per open claim on that key. Even if only a tenth of peak lands on the hot pair, 400 reads per second times 2,000 rows is 800,000 index tuples per second for one item at one node, and it gets worse exactly as the key gets hotter. A partial index on status IN ('held','committed') does not help, because the row count is the cost, not the selectivity.
- Maintaining available_qty on the position row is nearly free on the contention axis, which is the point most candidates miss. The allocator already takes a row lock or a compare-and-set on that same position row, so the extra column costs no lock that was not already held, and the read collapses to a single primary-key probe well inside 20 ms.
- It is not free on the correctness axis. available_qty becomes a second derived value that can drift, so it joins qty under the same reconciliation job with its own re-sum against the allocation table, and a drift alert on that comparison is the operational price of the read speed.
- Split the two reads rather than arguing about one. The browse read may serve from a replica or a short-TTL cache and must err low, showing less than is there. The allocate read must be on the primary and inside the same transaction as the conditional update, because replication lag peaks during the write burst that made the key hot. Conflating them is what makes a replica read look safe in a benchmark.
- Pick the TTL against the drain rate, with a number. If a promotion burns 2,000 units in a minute, a 30-second cache is a thousand units stale, so the displayed figure should be a banded signal such as in stock or low stock rather than an exact count that the allocate step will then contradict.
- Do not denormalise availability across nodes into a single network number. A yes-somewhere answer cannot be allocated against, so sourcing will disagree with the page, and the customer sees a promise retracted after checkout rather than an honest out-of-stock before it.
Worked solution 40 min
- Load a position with 2,000 open allocations and measure p99 for the derived read at 400 requests per second against the same read from a maintained available_qty column.
- Add available_qty, backfill it per key under the same lock the allocator takes, and write the re-sum query that proves it equals qty minus held and committed.
- Run both read paths in shadow for a fixed window, logging every disagreement with its key and magnitude.
- Point the browse read at a replica with a 30-second TTL and the allocate read at the primary, then measure how often the browse figure is higher than the allocatable truth.
Follow-up
- Roll this out on a live system. How do both paths run side by side, what do you compare, and what result gates the cut-over?
- The reconciliation job finds available_qty drifted by 3 on one key. What do you do, and what must you not do?
- Lot-tracked items multiply the position rows per item-node. Does your answer change, and where does the read now spend its time?
Write the reconciliation query that finds ledger and position variance
inventory_movement(movement_id, item_id, node_id, lot_id, state, delta_qty) is the ledger; inventory_position(item_id, node_id, lot_id, state, qty, version, last_movement_id, reconciled_through_movement_id) is its running sum, written in the same transaction as each movement. Write one query that reports every key whose stored qty disagrees with the sum of its movements, including keys present in only one of the two tables, bounded by a watermark movement_id you pass in. State what the job does with each variance, and how you avoid re-summing four billion rows every night.
Approach
- Aggregate once and join once: a CTE doing SELECT item_id, node_id, lot_id, state, SUM(delta_qty) AS ledger_qty FROM inventory_movement WHERE movement_id <= :w GROUP BY 1,2,3,4, then FULL OUTER JOIN inventory_position USING those four columns. One pass plus one hash aggregate, O(n) in movements scanned; a correlated subquery per position row instead costs one index scan per key and is orders of magnitude slower at 40 million keys.
- Use FULL OUTER JOIN, not LEFT. A key with movements and no position row is the signature of a lost position update, and a position row with no movements is the signature of a write that bypassed the ledger. Both disappear under a LEFT JOIN from either side.
- Compare with IS DISTINCT FROM and COALESCE the join keys out of both sides, so a missing row reads as a variance rather than as NULL = NULL evaluating to unknown and being filtered away.
- Run both sides in one snapshot: either a single statement, or a REPEATABLE READ transaction. Reading positions in one transaction and movements in another manufactures variances for every key written in between.
- Make it incremental with reconciled_through_movement_id: sum only movements in (reconciled_through, :w] and add that delta to the previously verified total. The catch is that sequence values are handed out before commit, so a movement whose id is below :w can become visible after the scan and would then never be re-summed. Set :w to a max(movement_id) observed a few minutes earlier, beyond the longest write transaction, and full-scan one partition per night on a rotation as a backstop.
- A variance is reported, not repaired. Rewriting qty to match the ledger hides the double-applied retry that caused it; only a physical count may produce a cycle_count_adjust movement, and the location should be flagged unreliable to planning until it is counted.
Follow-up
- The job finds 14 variances, all on one node, all negative. What do you look at first, and what would make you suspect the reconciliation job itself rather than the data?
- How do you run this without the nightly scan competing with the ATP read path for buffer cache and I/O?
- What does the query return for a key whose only movements are a receipt and its exact reversal, and is that row worth reporting?
What is your understanding of microservices architecture?
What is your understanding of microservices architecture?
Approach
- State your assumptions explicitly before working the problem.
- Work from the requirement backwards to the design.
- Say what you would check first and why it is the highest-information step.
Follow-up
- What assumption would you test first?
- How would you know your answer was wrong?
How do you manage code branching in a team environment?
How do you manage code branching in a team environment?
Approach
- Clarify what is being asked and what a complete answer contains.
- State your assumptions explicitly before working the problem.
- Say what you would check first and why it is the highest-information step.
Follow-up
- How would you know your answer was wrong?
- What assumption would you test first?
How do you ensure code quality and maintainability in your projects?
How do you ensure code quality and maintainability in your projects?
Approach
- Work from the requirement backwards to the design.
- State your assumptions explicitly before working the problem.
- Say what you would check first and why it is the highest-information step.
Follow-up
- What assumption would you test first?
- How would you know your answer was wrong?
Fold late, duplicated scan events into leg status
Observations arrive from handhelds, fixed readers and partner feeds: 9,000 per second average, 40,000 at peak. Partner feeds resend whole windows nightly and offline devices deliver six hours late. Each event carries dedupe_key, subject_id, observed_status, occurred_at and received_at. Design ingestion and the fold that maintains shipment_leg.status: the stream partition key, the deduplication mechanism, the ordering rule, and the handling of an event that is a week late or stamped in the future. Size observation_event's storage and partitioning.
Approach
- Separate the two defences, because each alone leaves a defect. Deduplicate on dedupe_key with a unique constraint or a keyed store, then fold with a monotonic status lattice. Deduplication alone still lets a stale picked_up overwrite delivered; monotonic folding alone still double-counts a resent window in any per-event aggregate such as dwell or scan productivity.
- Partition the stream by subject_id, the leg, so every event for one leg lands in one partition and is handled by one consumer. Broker ordering is per-partition only, so keying by source or device puts two events for one leg on different partitions to be processed concurrently, and the lattice would then be applied by two writers racing. Keying by leg spreads well because a single leg's event rate is physically bounded by scanning; the residual risk is a partition carrying several thousand legs of one large load, which is a throughput problem to absorb, not an ordering one.
- Write the fold as: order candidates by occurred_at, compare status_rank, take the maximum, never assign by arrival. Keep superseded events with processing_status superseded instead of discarding them, because the discarded event is usually the one that explains a dispute. Since occurred_at is device-reported and sometimes wrong, the rank lattice plus clock_offset_ms is what makes the rule safe - an event whose occurred_at is in the future beyond tolerance still folds on its rank but is flagged and not trusted for timing.
- Set the lateness policy in hours, not seconds. Hold a per-partition watermark at received_at minus the allowance; events behind it are still applied to the leg, because the lattice makes that safe, but any aggregate already published from them - on-time performance, dwell, carrier scorecards - must be restated rather than silently mutated, so a restatement path is part of the design. Beyond the retention horizon the event is stored with processing_status rejected_late and folded into nothing.
- Size the store from the average, not the peak: 9,000 per second is about 780 million rows per day, roughly 150 GB per day at 200 bytes. Daily range partitions on occurred_at give 30 partitions for a 30-day hot window rather than hundreds, which keeps planning time sane. A late event writes into an older partition, so partitions cannot be detached on age alone - they close only after the lateness allowance. Index (subject_id, occurred_at) for the fold and the trace; a block-range index on occurred_at degrades here because insert order stops correlating with event time.
Worked solution 35 min
- Write the dedupe key and the store that enforces it, and set its retention against the partner's replay window.
- Write the status lattice as explicit ranks and the fold rule, then replay the same eight events in three different orders and confirm one identical final status.
- Push three events through the rule - six hours late, a week late, and occurred_at two days in the future - and name the processing_status each ends in.
- Compute rows per day, bytes per day, partition count at daily granularity, and state which query the chosen index actually serves.
Follow-up
- A partner replays a 24-hour window every night. What does that cost in the dedupe store, and how long must a dedupe key be retained to stay correct?
- On-time performance was published yesterday and a late pickup event arrives today. Restate it or leave it, and what does the consumer of that number need to know?
- Where does the fold's state live, and what happens when a consumer restarts in the middle of a partition?
One partner's leg statuses freeze while every other feed advances
Since 06:40 UTC no leg fed by one partner has changed status; every other feed is current. Ingest is healthy and CPU is flat, and observation_event rows from that source are still landing with current received_at and processing_status 'pending'. Consumer lag is growing on exactly one stream partition, whose head row has over 900 attempts and a byte-identical last_error on each one. Give the ordered diagnosis and the change that stops one unapplicable observation from stalling a partition again.
Approach
- Separate the partner not sending from us not applying, because both look identical to a customer. Rows landing with current received_at and processing_status 'pending' prove ingestion is fine and the fold is stuck; if received_at had gone stale instead, this would be a partner or transport incident and nothing in the consumer would be at fault.
- Read the blocked partition at its head, ordered by offset, and classify the error rather than counting it. An identical error string across 900 attempts is deterministic and implicates the payload; a varying set of connection resets, timeouts and 5xx implicates the downstream store. Only the deterministic case is a poison message.
- Reproduce outside the consumer by running the fold over that one event. The usual cause here is an observed_status the partner added that has no rank in the progress lattice, or a subject_id whose leg does not exist yet, so the fold throws before it can write.
- Decide the exit against the ordering contract, which is per subject and not per partition. Many legs hash into one partition, so blocking on one of them is a bug and not a guarantee. Park the row as processing_status 'rejected_malformed' with its offset in a dead-letter view, advance the offset, and block only the affected subject if the event is well-formed but not yet applicable.
- Make the retry policy classify instead of counting. Deserialisation failures, unknown enum values and constraint violations are terminal and must never be retried in place; IO errors and timeouts are retryable with bounded attempts and backoff. Alert on oldest-pending age per partition, which turns positive within seconds, rather than on lag, which drifts up slowly and looks like normal backlog.
Follow-up
- The parked event turns out to be a real delivered scan. How do you replay it after the code fix without double-applying, and what makes that safe?
- What should the customer-facing status show for legs that were queued behind it?
- The partition key is currently the leg id. What breaks if you key by partner instead, and what if you key randomly?
Four days sample coding, design, fundamentals and the practical rounds at deliberately shallow depth, which is enough to surface the topics you did not know were in scope. That map, rather than a guess made on day one, decides where the last three days go.
Prepare, practise & reflect
One practical outcome each day. Spend longer where you need it.
0 / 7 done01Coding, one pass at shallow depth
- Solve one problem from each of six families, an array with two pointers, hash counting, binary search, a tree traversal, a graph traversal and one dynamic program, under a hard twenty-minute cap with no extensions, marking each finished, late, or stalled.
- For every stall, write the exact move you could not make rather than the subject, so the note reads could not turn the recurrence into a loop rather than bad at dynamic programming.
- Fix nothing today. The value of the pass is the unfixed record.
Deliverable: Six timed attempts marked finished, late or stalled, each stall carrying a named blocking move.
Practice prompt ↗Practice prompt ↗Worked solution ↗02Design, one pass at shallow depth
- Spend twenty minutes each on three different shapes, a read-heavy feed, a write-heavy ingest path, and something needing a transaction across two entities, stopping each at requirements, interface and data model.
- After each, write the first question you could not answer, which is usually a number you could not estimate or a failure mode you had no vocabulary for.
- Mark which of the three you would be most relieved not to be asked, and treat that as data rather than as a preference.
Deliverable: Three shallow designs, each with the first unanswerable question written at the bottom.
Practice prompt ↗Practice prompt ↗03Fundamentals and the practical rounds
- Answer eight short questions in writing at four minutes each, covering the material that fills the gaps between the big rounds: what happens between a URL and a rendered page, what an index costs on write, when a process is preferable to a thread, and what conditions a deadlock requires.
- Do one thirty-minute practical task of the kind a take-home compresses: read an unfamiliar two-hundred-line file and write what it does, what you would change, and the one thing you remain unsure of.
- Score every answer fluent, correct but slow, or absent, and keep the absent ones visible.
Deliverable: Eight scored short answers and one written reading of unfamiliar code.
Practice prompt ↗Practice prompt ↗04The rounds that are about you, and the map
- Deliver three behavioural answers aloud against a timer, a conflict, a failure you owned, and a decision made without enough information, marking any that ran past three minutes or contained no number.
- Assemble the map: every marked item from days one to three on a single page, sorted by how likely it is to appear in your loop rather than by how uncomfortable it felt.
- Choose exactly two areas for the remaining three days and write down what you are deliberately abandoning.
Deliverable: A one-page scored map of the whole surface area with two areas chosen and the rest explicitly abandoned.
Practice prompt ↗Practice prompt ↗Worked solution ↗05First chosen area, to the depth you skipped
- Work the higher-ranked area in four focused blocks, choosing items one level above where you stalled rather than repeating what already works.
- After each block write the rule you extracted in one sentence with its precondition attached, since a rule carrying no precondition is exactly what fails under a variation.
- Re-attempt the day-one or day-two item that exposed this area and compare against the original timing.
Deliverable: Four worked blocks, a timed re-attempt against the original, and three one-sentence rules with preconditions.
Practice prompt ↗Practice prompt ↗06Second chosen area, where the gap is coverage rather than speed
- Treat the second area differently from the first. Day five drilled something you could already half-do; this one is usually a topic you had simply never met, so build one worked reference example end to end and keep it, rather than attempting six problems badly.
- Write down the vocabulary you were missing on day two or three, five terms at most, each with the one sentence that makes it usable in an answer rather than the textbook definition.
- Redo the shallow attempt that exposed this area and note whether you now fail later in the problem, because moving the failure point is the realistic gain from a single day and is worth more than a score that did not change.
Deliverable: One worked reference example for the newly covered area, a five-term vocabulary list, and a note on where the failure point moved.
Practice prompt ↗Practice prompt ↗07Reassemble the loop
- Sit two rounds back to back with no gap, ordering them so the area you chose second comes last, because the map was built from rested, isolated attempts and the loop will reach your weaker area when you are already spent.
- Write where the second round suffered from the first, which is normally the point at which structure collapses into narration.
- Reduce the week to one page holding only the rules you can state without reading them.
Deliverable: Mock notes on cross-round carryover plus a one-page card of rules you can recite from memory.
Practice prompt ↗Worked solution ↗Expand any day for tasks and deliverables. Your progress is saved on this device.
A migration is a cost you chose to pay, not an achievement. The story is what the old system made expensive, what you measured before committing, what kept serving traffic during the cutover, and what you would have done if the numbers had come back flat. Without those, a rewrite reads as taste.
Tell me about your background and relevant project experience.
Tell me about your background and relevant project experience.
Approach
- Give the blast radius: what could have broken, and what you measured.
- Close with what you would do differently, concretely.
- Name the disagreement and how you resolved it with evidence.
Follow-up
- What would you do differently if you ran that again?
- What did you decide not to do, and why?
Resolve a review disagreement over clamping a position
A pull request changes the pick path from a plain decrement to qty = GREATEST(qty - :n, 0) on inventory_position, because the CHECK (qty >= 0) constraint has been failing a few times a day and paging the on-call. The author argues the page is noise and a negative on-hand is meaningless anyway. You disagree. Describe a code review disagreement you had: the argument you made, how you kept it about the system rather than the person, what you conceded, and how it ended. State what the clamp destroys and what you proposed be done with the failures instead.
Approach
- Name the information the clamp destroys, in one sentence: the constraint failure is the only signal that the ledger and the shelf disagree, and GREATEST turns a detectable variance into a silent one that surfaces later as a picker standing at an empty location for an order the system promised.
- Concede the real half of the author's case immediately, because it is real: paging a human on a data condition that a human cannot fix at 3am is bad design. The disagreement is not whether the page should stop but whether stopping the page should also stop the evidence.
- Propose the version that satisfies both: catch the constraint violation at the application boundary, write a cycle_count_adjust movement with its own reason code and reference so the correction is a first-class row rather than an invisible edit, fail the pick task with a specific error the floor can act on, and move the alert from a page to a variance report per item-node pair.
- Anchor the argument in the append-only model rather than in taste. Positions are derived from inventory_movement, so a clamp makes the position stop equalling the sum of its movements and the reconciliation job now reports a permanent, unexplainable variance on that key.
- Say how you handled the disagreement mechanically: the comment thread, then a short synchronous conversation once it became clear the thread was circling, and a written decision so the next person reading the clamp knows why it is not there.
- State the concession you actually made, including if you lost. Losing a review and writing the detector anyway is a legitimate and common outcome worth describing plainly.
Follow-up
- The clamp has already been in production for a month. How do you find out how much stock it silently absorbed?
- Where should the adjustment movement get its reference, given that no count sheet exists at pick time?
- The author is more senior than you and unconvinced. What is your next move?
Unblock an engineer whose replayed feed double-counts
An engineer on your team consumes a partner status feed that resends a rolling 24-hour window every night. Their consumer folds each message into shipment_leg.status by last write wins, keyed by partner message id. Deliveries are flipping back to in transit each morning, and some receipts are counted twice. They have been on it two days and ask for help. Describe a time you unblocked someone: how you diagnosed it together, which of the two independent defects you pointed at, what you deliberately let them find, and how you checked the fix held.
Approach
- Start by getting the two defects named separately, because they are independent and fixing one hides the other: duplicates are a deduplication problem, and statuses regressing is an ordering problem. A consumer can be perfectly deduplicated and still flap.
- Fix the dedupe key first, since it is the cheaper of the two and it is wrong for a stated reason: a partner message id changes on resend, so it identifies the transmission rather than the observation. The stable key is the source plus the device or partner event sequence, enforced as a unique constraint on observation_event.dedupe_key so a replayed window becomes a no-op at the database rather than a judgement in application code.
- Then fix the fold: order by occurred_at, compare status_rank on the progress lattice and take the maximum, so a picked_up event arriving after delivered is recorded but cannot lower the leg. Keep the superseded observation rather than dropping it, because it is usually the row that explains a later dispute.
- Teach the diagnostic rather than the answer. Ask them to replay one leg's observations in shuffled order and assert the same final status, which is the property that makes the fold order-insensitive and is a test they can write in twenty minutes. Let them discover from their own data that occurred_at is sometimes wrong, which is why clock_offset_ms is stored beside it and why the lattice, not the timestamp, is what guarantees monotonicity.
- Check it held with data rather than with a green build: count legs whose status_rank decreased in the last day, expect zero, and keep it as a standing assertion. Say what you did with the rows the old consumer had already corrupted, because leaving them is a decision too.
- Be honest about the handoff. Say how long you spent, what you did not do for them, and whether they could explain the fix back to you afterwards, which is the only durable test that the unblocking worked.
Follow-up
- They ask whether broker ordering guarantees solve this for them. What do you say?
- An event arrives a week late and its status is below the current one. What should the pipeline do with it, and what should it tell anyone reading the leg?
- How do you repair the legs already corrupted, given the observations are all still stored?
- 01
Tell me about your background and relevant project experience.
- 02
A pull request changes the pick path from a plain decrement to qty = GREATEST(qty - :n, 0) on inventory_position, because the CHECK (qty >= 0) constraint has been failing a few times a day and paging the on-call. The author argues the page is noise and a negative on-hand is meaningless anyway. You disagree. Describe a code review disagreement you had: the argument you made, how you kept it about the system rather than the person, what you conceded, and how it ended. State what the clamp destroys and what you proposed be done with the failures instead.
- 03
An engineer on your team consumes a partner status feed that resends a rolling 24-hour window every night. Their consumer folds each message into shipment_leg.status by last write wins, keyed by partner message id. Deliveries are flipping back to in transit each morning, and some receipts are counted twice. They have been on it two days and ask for help. Describe a time you unblocked someone: how you diagnosed it together, which of the two independent defects you pointed at, what you deliberately let them find, and how you checked the fix held.
Is this an official Smartestenergy interview guide?
No. It is PracHub's own research and practice material for the Software Engineer role at Smartestenergy. Rounds and questions reflect what candidates have reported, not a process Smartestenergy has published, and they change over time. Confirm the current format and scope with your recruiter.
PracHub interview research ↗How difficult are the technical interviews?
The technical questions are generally focused on foundational concepts and your ability to explain your past work. If you have a solid grasp of your own experience and standard engineering practices, you will be well-positioned.
PracHub interview research ↗What is the company culture like?
Smartestenergy is a fast-paced environment focused on the energy transition. They value individuals who are self-starters and can contribute effectively to team goals.
PracHub interview research ↗How long does the hiring process typically take?
The process can move quickly, often within a few weeks from the initial screen. Be prepared to move through the stages once you start the conversation.
PracHub interview research ↗Should I focus on high-level architecture or low-level coding?
You should be prepared for both. Expect to discuss the high-level design of your projects as well as the specific technical choices you made at the code level.
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