Good Energy · Software Engineer
Updated · 2026-09-24

Good Energy Software Engineer
Interview Questions & Guide 2026

THE 60-SECOND BRIEF

The Software Engineer role at Good Energy is central to the company’s mission of driving the transition to a greener, decentralized energy system. You will be responsible for building and maintaining the digital infrastructure that empowers customers to engage with renewable energy services. Your work directly impacts how energy data is processed, how products are delivered to users, and how the company scales its technical operations in a competitive, mission-driven market.

Prepare in one language you know well enough to debug in rather than the one you think reads best. Under a clock an unfamiliar language costs you standard-library lookups and iteration mechanics, and that time comes out of your thinking budget, not your typing budget.

Good Energy candidates report 3 rounds · ≈ 3-5 weeks. The stages below are what candidates describe, not a published process.

Reserve inventory without overselling under concurrent allocationReconcile a movement ledger against derived positionsFold late, out-of-order scan events by event time

42 min read

Browse Software Engineer questions

See the practice prompts

16Practice promptsAcross five skill areas
3With worked solutionsIncluded in the practice prompts

The Software Engineer role at Good Energy is central to the company’s mission of driving the transition to a greener, decentralized energy system. You will be responsible for building and maintaining the digital infrastructure that empowers customers to engage with renewable energy services. Your work directly impacts how energy data is processed, how products are delivered to users, and how the company scales its technical operations in a competitive, mission-driven market.

This role requires a balance of technical precision and collaborative problem-solving. You will contribute to a team that values high-quality, maintainable code and modern development practices. Because Good Energy operates in a specialized sector, you will face complex challenges regarding data integrity, system reliability, and user-centric design. It is an environment where your ability to translate business goals into robust software solutions will be tested and highly valued.

Expect to work within a close-knit engineering culture where technical alignment is prioritized. You will not only be writing code but also participating in architectural discussions, mentoring peers, and ensuring that the software you build is resilient and aligned with the company’s long-term sustainability goals.

01

Initial Conversation

reported

Because the format is not fixed, the first job in the room is classification. Listen to the opening question and decide what it is: a probe into work you have already described, a fresh problem to solve now, or a conversation about how you operate. Each wants a different register, and the common failure is forcing a rehearsed structure onto a question that did not ask for it. Running a full design ritual on a ten-minute debugging question reads as not listening. When you cannot tell which it is, ask how long they want to spend and answer at that depth.

What to demonstrate

  • Whether the shape of your answer matches the question, so a yes-or-no gets answered before it is justified and an open prompt gets a direction before a detour
  • Whether you check how much depth is wanted instead of deciding for them, and whether you stop when the answer is complete rather than continuing until someone interrupts
  • Whether you can be redirected in the middle of an answer without restarting it from the beginning
  • Whether a question outside your experience gets an honest boundary followed by reasoning from what you do know, instead of a confident answer with nothing behind it

How to prepare

  • Rehearse one project at three lengths, roughly thirty seconds, three minutes, and a full walkthrough at the depth of a design review, and practise switching between them when someone interrupts mid-telling
  • Have someone ask you five questions of deliberately mixed type in one sitting without telling you the types, and score only whether you identified each one correctly before you started answering
  • Draft the sentence you will use to check depth, along the lines of asking whether the short version is useful here or they want the detail, and use it in a real conversation this week so the day of the round is not its first outing
PracHub interview research ↗
02

Technical Evaluation

reported

Input bounds are the part of the prompt most often skimmed, and they usually contain the answer. They tell you which complexity class is admissible, which narrows the search before you have thought about the problem itself. As a rough planning figure, a compiled language does on the order of 10^8 simple operations per second and an interpreted one roughly an order of magnitude less. So n up to about twenty admits enumerating subsets, a few thousand admits a quadratic pass, and a million admits neither: you need near-linear, or linear with a log factor. If the bounds are missing, ask for them.

What to demonstrate

  • Whether the approach is justified by the stated input size rather than by whichever pattern you recognised first
  • Whether you ask about the properties that change the algorithm: whether the input arrives sorted, whether duplicates occur, whether values are bounded integers, whether it all fits in memory
  • Whether you can name the bottleneck in your own solution and what would remove it, even when you deliberately leave it in place
  • Whether a claimed speedup is real, since memoising a recursion only helps when subproblems genuinely overlap and the state can be keyed cheaply

How to prepare

  • For each algorithm you rely on, write down the largest n it handles in roughly a second, then check two of those figures by timing them in the language you will actually type in
  • For two weeks, write one line naming your target complexity and the bound that justifies it before you write any code, then compare that line with what you ended up submitting
  • Practise the conversion backwards: given a required O(n log n), list the mechanisms that get you there (sorting, a heap, an ordered map, divide and conquer) and choose by what the problem needs to query, not by what you used last
PracHub interview research ↗
03

Final Stage Review

reported

Nobody 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 interview research ↗

PracHub editorial advice for the preparation topics above.

01

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.

02

Treating the database as the truth about physical stock

Every system in this domain drifts from reality, through miscounts, unrecorded damage, mis-scans and theft, so a design with no adjustment path forces the drift somewhere worse. The usual shape is a CHECK constraint or application guard that clamps a position at zero: the negative that would have exposed a missing receipt is silently swallowed, and the first visible symptom is a picker standing at an empty location for an order the system promised. The correct posture is to let the invariant fail loudly - record the cycle-count adjustment as a first-class movement with its own reason code, surface the variance, and let planning see that this location is unreliable - because an adjustment that is indistinguishable from a normal pick destroys the only signal that would have found the root cause. A related version of the same mistake is deleting or updating a movement row to fix a mistake, which leaves the position correct and the history a lie.

03

Assuming the bug is in the framework

Suspect your own code first: read the stack trace top to bottom, check which versions are actually installed rather than which ones you believe are, and reproduce in isolation before blaming a library that thousands of people run daily. When the fault really is upstream, you need that minimal reproduction to say so credibly anyway.

04

Sharing mutable state with no stated owner

Say which thread, request or task owns each mutable structure, and what protects it when the answer is more than one: a lock, a queue that hands ownership across, or an immutable copy per reader. A structure documented as safe for concurrent reads is usually not safe for a concurrent write alongside those reads.

Choose a category, try a prompt, then open its approach, worked solution or follow-up when you need it.

13 technical prompts3 include a worked solution

Discussion on technical challenges you have faced in previous roles.

medium
data structures and algorithms

Discussion on technical challenges you have faced in previous roles.

Approach
  1. Name the brute-force solution and its complexity before improving on it.
  2. Choose the data structure from the access pattern, not from familiarity.
  3. State the target complexity and say which constraint rules the naive version out.
Follow-up
  • How does this change if the input no longer fits in memory?
  • Which test case would catch an off-by-one here?

How do you balance code quality (e.g., unit testing) with project time…

medium
data structures and algorithms

How do you balance code quality (e.g., unit testing) with project time constraints?

Approach
  1. Restate the input: its shape, its size, and what is guaranteed about it.
  2. Walk one small example through your approach before writing the whole thing.
  3. Name the brute-force solution and its complexity before improving on it.
Follow-up
  • Which test case would catch an off-by-one here?
  • How does this change if the input no longer fits in memory?

Questions regarding your technical test: why you chose a certain appro…

medium
data structures and algorithms

Questions regarding your technical test: why you chose a certain approach and what alternatives were considered.

Approach
  1. State the target complexity and say which constraint rules the naive version out.
  2. Choose the data structure from the access pattern, not from familiarity.
  3. Walk one small example through your approach before writing the whole thing.
Follow-up
  • How does this change if the input no longer fits in memory?
  • Which test case would catch an off-by-one here?

Split an order across the fewest nodes that can fill it

hardWorked solution
set coverbitmask dpapproximation

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
  1. 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.
  2. 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 in rem, with dp[0] = (0, 0) and the value a (node_count, total_distance) pair compared lexicographically. Let b be the lowest set bit of rem; any cover must contain some node that stocks line b, 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.
  3. Quote the memory for what you actually store, not for a count array alone. Over 2^25 masks you need three dense arrays: uint8 node count (33.5 MB), uint32 total distance (134.2 MB, integer metres so the tie-break compares exactly and the array stays fixed-width), and uint8 chosen 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.
  4. Reconstruction is free in this formulation and needs no parent-mask array: because every transition eliminates the lowest uncovered line, the predecessor of rem under the stored choice v is exactly rem & ~cover(v), so walk rem from the full mask down to 0 emitting choice[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 of ceil(popcount(rem) / max_v popcount(cover(v) & rem)), which prunes long before the theoretical bound bites.
  5. 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.
  6. 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.
Worked solution 40 min
  1. Write cover(v) as an L-bit mask per node and confirm that the union of all masks equals the full mask, otherwise the order is unfillable and the answer is a backorder, not a cover.
  2. Implement the DP over uncovered masks with the lowest-uncovered-line transition, the (node_count, total_distance) value and the choice array, then measure resident memory and wall time at L = 20 and L = 25 against the quoted 6.3 MB and 201 MB.
  3. Implement greedy separately and run both on 10,000 random instances with L = 12, N = 10, comparing sizes.
  4. Construct the textbook instance where greedy is strictly worse than optimal — nested sets of geometrically decreasing size plus two disjoint halves — and confirm your greedy reproduces the gap.
  5. Add the distance tie-break to both and confirm the exact path returns the cheapest minimum-size cover, not merely a minimum-size one.
EXPECTED RESULTThe DP and greedy agree on the large majority of random instances; on the adversarial instance greedy returns more nodes than the DP, by a margin consistent with the H(L) bound; the DP at L = 25 holds about 201 MB across its three arrays and runs in seconds rather than milliseconds, with cache misses over that working set dominating the 4.7e8 transitions.
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?

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.

Small steps. Visible outcomes.0 / 7 completed
ONE WEEK · YOUR PACE

Prepare, practise & reflect

One practical outcome each day. Spend longer where you need it.

0 / 7 done
01Coding, 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 ↗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 ↗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 ↗Practice prompt ↗Worked solution ↗

Expand any day for tasks and deliverables. Your progress is saved on this device.

A slipped date is only a bad story if you sat on it. What matters is what you believed when you gave the number, the signal that told you it was wrong, how many days passed before you said so, and what you cut rather than asking for more time. Scope you defended counts as much as scope you dropped.

Reverse an allocation design after peak contention

medium
concurrency controllock contentionreversibilitymeasurement

You chose optimistic concurrency for allocation: read the position, then UPDATE inventory_position SET qty = qty - :n, version = version + 1 WHERE version = :v, retrying on an affected-row count of zero. It was correct and fast in load tests with spread keys. At peak, a few hundred hot item-node pairs absorbed most write traffic, retries amplified, and allocation p99 went past the checkout budget. Describe a decision you reversed under production evidence: what you originally reasoned, the measurement that forced the change, what you replaced it with, and what you would have measured before committing.

Approach
  1. Say why the original choice was reasonable, because a reversal story is only useful if the first decision was defensible. Compare-and-set avoids holding a lock across the read, has no deadlock surface, and is uncontended on the long tail of keys, which is most keys most of the time.
  2. Name the mechanism of the failure rather than calling it contention. On PostgreSQL under READ COMMITTED, a conflicting UPDATE does not fail fast: it blocks on the row lock until the other transaction commits, then re-evaluates its predicate against the new row version and reports zero rows affected. Each loser therefore pays a full lock wait before learning it must retry, so with k writers queued on one pair the work is quadratic in k across the burst, and the retry loop adds round trips rather than avoiding waits.
  3. Bring the measurement that settled it, not the anecdote: attempts per successful allocation on the hottest pairs, the distribution of write traffic across item-node keys, and the p99 contribution of lock wait time separated from query time. Load tests with spread keys cannot show any of this, which is the real lesson and the thing you would run differently.
  4. State the replacement and its cost. Serialising each hot key behind a single writer with a bounded queue converts an unbounded retry storm into a bounded wait plus explicit shedding, at the cost of a new component, a routing decision and a failure mode when the writer for a key is unavailable. SELECT ... FOR UPDATE is the smaller change and trades the retry loop for an in-database queue that still consumes a connection per waiter.
  5. Describe the migration, since reversing a write path in production is where these stories become concrete: route only the measured hot keys first, keep both paths live behind a per-key decision, and verify with the same attempts-per-success metric before widening.
  6. Close on what you would have measured before committing, and be specific: key skew from production traffic, not from a synthetic generator, is the input the original decision was missing.
Follow-up
  • Under REPEATABLE READ on PostgreSQL that same conflict raises a serialization failure instead. What changes in your retry code and your error budget?
  • One key becomes so hot that even the single writer saturates. How do you shed load without overselling?
  • An allocation must span two positions atomically. What breaks in your replacement design if those rows live on different shards?

Own an outage that double-tendered loads to a carrier

medium
incident responseidempotencyblast radiuscarrier integration

A retry wrapper in the carrier gateway re-issued tenders whose responses had timed out, minting a fresh idempotency key on each attempt. Over 70 minutes, 212 of 1,340 legs were tendered twice and 19 became second physical pickups before dispatchers noticed. Take the on-call role and describe an outage you owned with a comparable blast radius: how it was detected, how you bounded the affected legs, what you stopped first, and how the duplicate physical work was unwound. Give a wall-clock timeline, the query that sized the damage, and the guard that would have caught it in minutes rather than an hour.

Approach
  1. Open with the invariant that broke rather than the symptom: at most one booking exists per shipment_leg, keyed by booking_idempotency_key. Stating the invariant tells the listener what is countable, which is what makes the rest of the story checkable.
  2. Size the population with a named query, not an adjective. Duplicates are visible two ways and a strong answer says which one it trusted: legs carrying more than one distinct carrier_booking_ref, and the carrier's own booking export left-joined to shipment_leg on the reference, where the unmatched rows are bookings the system does not know it owns. The second is authoritative because the partner, not you, holds the truth about what was accepted.
  3. Separate mitigation from fix and say which came first. Mitigation is blunt and cheap: disable the retry path, drop gateway concurrency, hold tendering behind a flag so the floor keeps picking while dispatch stops booking. The fix, minting and persisting the key before the call with an explicit unknown status, is not an incident-window change.
  4. Split the blast radius into tiers, because the recovery cost is not uniform: a duplicate record is a delete, a duplicate booking not yet dispatched is a cancellation call, and a duplicate that became a pickup is a physical reversal with a trailer, a driver and an invoice behind it. The 19 that moved dominate the cost and belong at the front of the account.
  5. Close with one detector and its price. A counter on bookings-per-leg with an alert above one, or a nightly reconciliation of the partner export against shipment_leg, are both defensible; naming one and saying how many false pages per week it costs beats listing five that nobody staffed.
  6. Name what you got wrong inside the response window, not only in the writeup. Interviewers weight that heavily because it is the part candidates rehearse away.
Follow-up
  • The partner has no idempotency concept on their tender endpoint. What do you do differently, and what is the residual exposure you accept?
  • How would you have detected this in five minutes instead of 70, and what would that detector have cost in noise?
  • Dispatch disputes your count of duplicate pickups. What artefact do you show them?

Turn an implicit fail-open availability answer into a decision

medium
degradationfail opendecision recordspartial failure

When the inventory service is unreachable, the availability client returns the last value it cached, because a broad except block around the call does that by accident. Nobody chose it. During a 40-minute outage the storefront kept promising stock for items that had sold out, and 900 lines short-shipped. Describe a time you converted an implicit behaviour into an explicit decision: how you surfaced it, the two directions the error can go and what each costs, what you actually chose for the checkout path versus a browse page, and where the choice is now written down.

Approach
  1. Surface the behaviour as a fact before arguing about it: the except block is a policy, it was never chosen, and it currently applies identically to a browse page and to the promise made at checkout. Framing it as an unowned decision rather than as someone's bug is what gets it looked at.
  2. Price both error directions with the operations they trigger, since neither is free. Promising stock you do not have produces a short-ship, a customer contact, a re-source or refund and, on the floor, a picker sent to an empty location; refusing stock you do have loses the order outright. Both are real, the second is cheaper to reverse, and that asymmetry is the argument.
  3. Split the decision by path instead of choosing one policy for the system. A browse page can serve a stale value with a staleness label and be right most of the time; a checkout promise should degrade to a conservative answer, which is not zero but the last known position reduced by an in-flight allowance sized from the recent allocation rate for that key. Applying one policy to both paths is the actual defect.
  4. Bound the staleness, because a cached value with no age is the same bug wearing a different hat. Serve the cached number while it is under a stated age, then switch to the conservative answer, then refuse; the thresholds need owners and should appear on a dashboard as the share of reads answered from each tier.
  5. Write it down where the next person meets it: a short decision record giving the behaviour, the two costs, the chosen policy per path and the date, plus a test that asserts the checkout client cannot serve an unlabelled stale value. A decision that lives only in a thread gets reverted by the next broad except block.
  6. Report what the change actually cost. If the conservative answer suppressed some orders that would have been fillable, say how many, because a degradation policy that was never measured is indistinguishable from one that was never enforced.
Follow-up
  • How do you size the in-flight allowance, and what happens to it during a promotion on that item?
  • The outage is 4 hours rather than 40 minutes. Does your policy still hold, and what changes at which threshold?
  • Someone adds a new consumer of the availability client next quarter. What stops them inheriting the wrong policy by default?
  • 01

    You chose optimistic concurrency for allocation: read the position, then UPDATE inventory_position SET qty = qty - :n, version = version + 1 WHERE version = :v, retrying on an affected-row count of zero. It was correct and fast in load tests with spread keys. At peak, a few hundred hot item-node pairs absorbed most write traffic, retries amplified, and allocation p99 went past the checkout budget. Describe a decision you reversed under production evidence: what you originally reasoned, the measurement that forced the change, what you replaced it with, and what you would have measured before committing.

  • 02

    A retry wrapper in the carrier gateway re-issued tenders whose responses had timed out, minting a fresh idempotency key on each attempt. Over 70 minutes, 212 of 1,340 legs were tendered twice and 19 became second physical pickups before dispatchers noticed. Take the on-call role and describe an outage you owned with a comparable blast radius: how it was detected, how you bounded the affected legs, what you stopped first, and how the duplicate physical work was unwound. Give a wall-clock timeline, the query that sized the damage, and the guard that would have caught it in minutes rather than an hour.

  • 03

    When the inventory service is unreachable, the availability client returns the last value it cached, because a broad except block around the call does that by accident. Nobody chose it. During a 40-minute outage the storefront kept promising stock for items that had sold out, and 900 lines short-shipped. Describe a time you converted an implicit behaviour into an explicit decision: how you surfaced it, the two directions the error can go and what each costs, what you actually chose for the checkout path versus a browse page, and where the choice is now written down.

PracHub interview preparation framework ↗
Is this an official Good Energy interview guide?

No. It is PracHub's own research and practice material for the Software Engineer role at Good Energy. Rounds and questions reflect what candidates have reported, not a process Good Energy has published, and they change over time. Confirm the current format and scope with your recruiter.

PracHub interview research ↗
How long should I spend on the technical exercise?

While the exercise is intended to be comprehensive, focus on quality over quantity. It is better to deliver a smaller, high-quality, well-tested solution than a large, buggy, or untested one.

PracHub interview research ↗
Is the interview process difficult?

The difficulty is generally considered average, but the rigor applied to the technical assessment is high. Expect to be challenged on the details of your implementation.

PracHub interview research ↗
What is the team culture like?

The team is described as close-knit and professional. They value technical expertise and a collaborative, mission-driven mindset.

PracHub interview research ↗
How long does the entire process take?

While it can vary, the three-stage process is designed to be efficient but thorough. You can typically expect a few weeks from the initial screen to a final decision.

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.