A Software Engineer at Hopper is responsible for building and scaling the proprietary real-time algorithms, fintech products, and high-throughput microservices that power one of the world's fastest-growing travel marketplaces. Engineers at Hopper do not just build booking interfaces; they design and maintain the complex systems that process billions of travel data points daily to predict prices, manage risk, and deliver products like Price Freeze, Cancel for Any Reason, and Flight Disruption Guarantee.
The engineering team has migrated the majority of its legacy Python and Golang services over to Scala, making functional programming and JVM optimization central to the engineering culture. Whether you are optimizing search engines for the APAC flights team or building resilient infrastructure to support massive global demand, your work directly impacts millions of travelers. This requires a unique blend of algorithmic efficiency, system design expertise, and a product-focused mindset.
At Hopper, engineering is highly decentralized and metric-driven. Teams operate with a high degree of autonomy, meaning you will have direct ownership over your services from architectural design to production monitoring. It is a fast-paced environment where technical decisions are guided by data, and engineers are expected to balance rapid product delivery with long-term system maintainability.
Recruiter Call
reportedBefore anything technical happens, someone has to decide which rung of the ladder your loop is calibrated to, and that decision sets the bar for every round after it. It comes from how you describe scope, not from your title, because titles do not convert cleanly between companies. The weak version of the answer is team size and years. The strong version names the largest change you shipped where nobody reviewed the design, what would have broken if you had been wrong, and what you were paged for. Get the level said out loud on this call, because the range and the loop both follow from it.
What to demonstrate
- Whether the scope in your own account maps onto a level the team actually has an opening at, so a mismatch ends the process cheaply rather than after four interviewers have spent a day
- Whether your title needs re-mapping: the same word describes very different amounts of independent decision-making at a twenty-person company and a ten-thousand-person one
- Whether your compensation expectation can be filled at that level in the structure the role pays in, which is why the number gets asked for before any engineer is scheduled
How to prepare
- Write down two changes from the last two years: the largest one you designed with nobody reviewing the design, and the largest one where someone more senior did. Lead with the first when scope comes up, and be ready to say which parts of the second were yours
- Ask which level the loop is calibrated to and what changes at the level above it, then plan your weeks from that answer rather than from the posting
- Settle a total-compensation range beforehand with the split named, base against bonus against equity and its vesting period, so a question about numbers gets a number instead of the word market
Online 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
Technical Phone Screen
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
Virtual Onsite Loop
reportedCoding rounds mostly set a floor. They decide whether you clear the bar, not where you land on the ladder. Level tends to come out of the design discussion and the ownership stories, so the question worth auditing beforehand is whether the scope you describe matches the scope of the job. Work that stops at your own service, or a story whose hard part was writing the code rather than getting several people to agree on an interface, reads a level below where you think you are interviewing, and that gap is usually resolved downwards.
What to demonstrate
- Whether the largest thing you describe owning ran end to end — the decision, the migration path, the rollout, and what you did when it went wrong — or stopped at the change you merged
- Whether design answers include what you would not build, what you would defer, and what you would measure before committing, rather than only what the boxes are
- Whether a disagreement in a story was settled with something checkable — a benchmark, a prototype, a written proposal — instead of by seniority or by waiting it out
- Whether you can say which calls you made alone and which you escalated, and why the line sat where it did
How to prepare
- Write your largest piece of owned work as a timeline of decisions — who decided what, when, and what you did when the plan broke — then delete every sentence whose subject is "we" and see how much survives
- Take one system you know well and drill the migration answer: how old and new paths run side by side under live traffic, how you compare their outputs, what the rollback is once writes are going to both, and which step you would not automate
- Map each line of the ladder in the job posting to a specific thing you have done, find the line you cannot support, and prepare the closest evidence you have plus an honest account of the gap
Final Bar Raiser
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.
Representing money as a float, and rounding once at the end
Currencies have different ISO 4217 exponents, so the reflex of multiplying by 100 is wrong for zero-decimal currencies such as JPY and KRW and for three-decimal currencies such as KWD, BHD and OMR, and it is wrong in a direction that gets worse with volume. Separately, taxes and fees are levied per night and per jurisdiction and each rounds on its own, so the sum of rounded per-night amounts is not the rounded sum, and the two differ by a few minor units on a typical stay. Neither bug shows up in testing with a single currency and a single-night booking. Both show up as a reconciliation mismatch against the supplier or the processor, at which point somebody has to explain a line-item discrepancy across thousands of bookings without a per-night audit trail to do it with.
Caching search results at the (property, date range, party) grain
That key is the cartesian product of destination, check-in, length of stay, party composition, currency, point of sale and promotion eligibility, so the hit rate is close to zero on anything but the most popular routes and the cache pays for itself only in memory. Worse, any input you forget to include in the key leaks one traveller's price to another, and price leaks of that kind are discovered by customers rather than by monitoring. Cache at the (unit_type, stay_date) grain instead: the key space is bounded by unit types times the forward horizon, every entry is reused across every length of stay and every flexibility variant that touches that night, and range composition plus restriction evaluation happens at request time on cheap in-memory rows. The tradeoff is that invalidation now has to be precise per unit-date, which is the right problem to have.
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.
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.
Choose a category, try a prompt, then open its approach, worked solution or follow-up when you need it.
Route Pathfinder: Given a list of starting locations, destinations, an…
Route Pathfinder: Given a list of starting locations, destinations, and intermediate connections, recursively find all possible paths between two specific locations.
Approach
- Walk one small example through your approach before writing the whole thing.
- Name the brute-force solution and its complexity before improving on it.
- State the target complexity and say which constraint rules the naive version out.
Follow-up
- Which test case would catch an off-by-one here?
- How does this change if the input no longer fits in memory?
Stock Price Calculator: Implement a system that tracks fluctuating sto…
Stock Price Calculator: Implement a system that tracks fluctuating stock prices over time and efficiently returns the maximum profit possible from a single buy-and-sell transaction.
Approach
- Name the brute-force solution and its complexity before improving on it.
- State the target complexity and say which constraint rules the naive version out.
- Choose the data structure from the access pattern, not from familiarity.
Follow-up
- Which test case would catch an off-by-one here?
- How does this change if the input no longer fits in memory?
Spell-Checker Engine: Design and implement a real-time spell-checker u…
Spell-Checker Engine: Design and implement a real-time spell-checker using an optimal data structure (such as a Trie) to suggest corrections within a given timeframe.
Approach
- State the target complexity and say which constraint rules the naive version out.
- Restate the input: its shape, its size, and what is guaranteed about it.
- Walk one small example through your approach before writing the whole thing.
Follow-up
- Which test case would catch an off-by-one here?
- How does this change if the input no longer fits in memory?
Expand a supplier rate push into unit-date rows
Parse a supplier availability and rate push. Each line is ARI|<supplier_unit_code>|<start>/<end>|<dow_mask>|<seq>|<k=v>,... where the date range is inclusive at both ends, dow_mask is seven characters for Monday through Sunday, and keys come from price, cur, minlos, maxlos, cta, ctd, stop, avail. Expand each line into (unit_type_id, stay_date) updates carrying supplier_seq = seq, dropping any row whose stored seq is greater than or equal to seq. Prices arrive as decimal strings and must become minor units at the currency's ISO 4217 exponent. Reject malformed lines with a reason. Target O(input characters + rows emitted).
Approach
- Split each line on
|and validate the field count before interpreting any field. A line with the wrong arity is rejected whole; lenient parsing here produces rows that look plausible and are wrong, which is worse than a rejection nobody can miss. - Parse
startandendas civil dates and iterate with a civil-date successor, never by adding 86,400,000 ms to an instant. Validateend >= startand that the span is at most the 450-day horizon before expanding, so a malformed range cannot drive an unbounded loop. - Compute the weekday of each civil date directly — a civil date has exactly one weekday and needs no timezone to determine it — and index the mask with Monday at position 0. Apply the mask before doing any per-row work.
- Convert the price by shifting the decimal point, not by float multiplication. Split on
., require the fractional part to be no longer thanexponent(cur), right-pad it to exactly that length, and concatenate.12.5with KWD (exponent 3) becomes 12500 minor units;1250.00with JPY (exponent 0) is a rejection, because JPY has no fractional part to round into and silently truncating it changes the price by a factor of 100. - Apply the seq guard per
(unit_type_id, stay_date), not per message. A single date inside the range may already carry a newer delta while the rest of the line is fresh, and dropping or applying the whole line on one comparison is wrong in both directions. - Stream rows to the writer rather than buffering per line: one pass, O(characters) for parsing plus O(1) per emitted row, and O(1) working space independent of the 450-day maximum expansion.
Worked solution 30 min
- Take four lines. (1)
ARI|DLX-KING|2026-03-01/2026-03-07|1111100|88231|price=12500,cur=JPY,minlos=2,cta=1. (2)ARI|DLX-KING|2026-03-01/2026-03-07|0000011|88232|price=1250.00,cur=JPY. (3)ARI|STD-TWIN|2026-03-01/2026-03-03|1111111|41000|price=12.5,cur=KWD,avail=4. (4)ARI|GONE-999|2026-03-01/2026-03-02|1111111|900|price=100,cur=USD. - Establish the weekdays: 2026-03-01 is a Sunday, so the range runs Sun, Mon, Tue, Wed, Thu, Fri, Sat.
- Apply each mask and count the surviving dates per line before any other validation.
- Validate currencies and prices: JPY has exponent 0 and KWD has exponent 3, and DLX-KING and STD-TWIN are in the mapping table while GONE-999 is not.
- Apply the per-row seq guard against a stored map containing exactly one entry, (DLX-KING, 2026-03-04) -> 88300, and tally the final counts.
Follow-up
- The same message arrives twice, and once out of order relative to a later delta. Show which rows change on each delivery.
- A supplier sends
minlos=3on the middle night of a range. Which stay is affected, given that minimum length of stay is read from the arrival night's row? - How would you report a line that is syntactically valid but semantically absurd — a price two orders of magnitude off the unit's recent range — without blocking the rest of the message?
Stop two checkouts claiming the last unit on one night
ari_daily(unit_type_id, stay_date, remaining_units) has PRIMARY KEY (unit_type_id, stay_date) and CHECK (remaining_units >= 0); unit_type carries physical_unit_count and oversell_allowance; inventory_hold_night(hold_id, stay_date, unit_type_id, units, state, expires_at_utc) records claims, and availability is currently computed as remaining_units minus active holds. Two checkouts each ask for two units over 2026-03-12 to 2026-03-15 while three remain. Write the statements that take the hold atomically, name the isolation level you are assuming and what PostgreSQL does to your statement under it, and give the release path.
Approach
- Notice first that the invariant as stated cannot be enforced by a single-row compare-and-set: remaining_units sits on ari_daily while the held quantity is an aggregate over another table, and no one statement spans both. Materialise held_units on the ari_daily row so the check collapses onto one row, and keep inventory_hold_night as the per-hold ledger that makes release idempotent. That is a deliberate denormalisation with a cost, not a shortcut.
- Claim each night with one conditional UPDATE whose predicate is the capacity check: UPDATE ari_daily SET held_units = held_units + n WHERE unit_type_id = u AND stay_date = d AND remaining_units - held_units >= n. Inspect the row count of every statement before committing; zero means the night is gone and the transaction rolls back rather than continuing with a partial hold. A SELECT followed by an UPDATE cannot work, because both readers see three units before either writes.
- Issue one statement per night in ascending stay_date order, and across unit types by unit_type_id then stay_date. A single multi-row UPDATE locks rows in whatever order the chosen plan returns them, and an index scan and a bitmap heap scan answer that differently, so plan shape silently becomes your lock order. Two overlapping ranges taken in request order deadlock, and one side dies with SQLSTATE 40P01.
- Name the isolation level as a decision rather than inherit it. Under READ COMMITTED a blocked UPDATE re-reads the newly committed row version and re-evaluates its WHERE clause, so the loser's predicate now fails and it updates zero rows with no retry needed. Under REPEATABLE READ or SERIALIZABLE the same statement instead raises serialization_failure, SQLSTATE 40001, and the caller must retry the whole transaction within a bounded budget. Same SQL, two different code paths.
- Make release a state transition that gates the decrement: UPDATE inventory_hold_night SET state = 'released' WHERE hold_id = h AND stay_date = d AND state = 'active', and subtract from held_units only when that statement reports one row, in the same transaction. A bare subtraction is not idempotent, and an expiry sweeper racing a conversion applies it twice, selling a unit that does not exist.
- Treat the CHECK as the last line of defence rather than the control. If it ever fires you get a constraint violation on a booking that should have been refused cleanly, and the ceiling is physical_unit_count plus oversell_allowance, so the predicate has to compare against the allowance-inclusive figure rather than against raw capacity.
Follow-up
- Holds expire on wall-clock time that no transaction observes. Does the read ignore expired holds or does a sweeper release them, and what does each choice cost in oversell versus understated availability?
- Under REPEATABLE READ, what is your retry budget, and how do you stop a retry storm from making the contention worse?
- The third night fails after the first two succeeded. What have you already written, and what does the compensation look like?
Reconcile night revenue against captures without fanning either out
booking(booking_id, unit_type_id, state, total_minor, currency_code) has booking_night(booking_id, stay_date, unit_type_id, night_price_minor, tax_minor, fee_minor, state) one-to-many, and payment_transaction(payment_transaction_id, booking_id, kind, state, amount_minor) one-to-many. A report joins all three and sums, and on a three-night booking with a deposit plus a balance capture both totals are wrong. Name both multiplications precisely, then write the query giving nights sold, night gross and net captured per property per stay month, and state how a booking-grain capture is attributed to the months its nights fall in.
Approach
- Name the cardinality: booking to booking_night is one-to-N and booking to payment_transaction is one-to-M, so joining both produces N times M rows per booking. Every night is counted M times and every payment N times, and the two columns are inflated by different factors, which is why the report looks plausible rather than obviously broken. The defect is aggregating a measure across a row set two children have expanded, not the SUM itself.
- Reject the reflexive fixes. DISTINCT and SUM(DISTINCT ...) de-duplicate values rather than rows, so two genuine nights priced identically collapse into one and the total becomes wrong in the other direction while looking tidier.
- Aggregate each child in its own CTE before joining anything, and filter on kind and state rather than on sign: the capture total is SUM(amount_minor) FILTER (WHERE kind = 'capture' AND state = 'succeeded') minus the same expression for refunds, grouped by booking_id. Authorisations are not revenue and 'unknown' is not a failure, so neither may be counted. SUM with FILTER returns NULL rather than zero when nothing matches, so wrap both sides in COALESCE before subtracting or a booking with no refund reports nothing at all.
- Face the grain mismatch instead of joining through it. Nights are the consumption grain and a capture is at booking grain, so no join makes them directly comparable. Either report the two at their own grains and reconcile explicitly, or allocate each booking's net capture across its nights in proportion to night gross, in integer minor units, distributing the remainder largest-remainder first with a stable tiebreak on stay_date.
- Take the month from booking_night.stay_date and never from booking.created_at, because one booking's nights routinely fall in two months and two tax periods. date_trunc over a date argument returns a timestamp, so cast back to date if the grouping key is meant to be one.
- Filter on booking_night.state rather than booking.state, since a booking can be partially cancelled and the night rows are where that is recorded. Assert that the sum of night price, tax and fee equals booking.total_minor per booking as a test, because per-night, per-jurisdiction rounding is exactly where that identity breaks.
Worked solution 30 min
- Build a fixture with one booking, three nights spanning a month boundary, one deposit capture and one balance capture.
- Run the naive three-way join and confirm each night row is duplicated twice by the two payments while each payment row is duplicated three times by the three nights, giving six rows in place of three nights and two payments.
- Rewrite with two CTEs aggregated to booking grain, then allocate net capture across nights by night gross with a largest-remainder pass.
- Group to property and stay month, then reconcile the grand totals independently against booking_night and against succeeded captures.
Follow-up
- A refund lands two months after the stay. Which month's net moves, and does finance want the same answer as the payout file?
- The allocation leaves three minor units unassigned on a zero-decimal currency booking. Where do they go, and is the rule stable across re-runs?
- From the result alone, how would you prove that no booking was expanded by a join?
Cloud Infrastructure Migration: Describe how you would design a resili…
Cloud Infrastructure Migration: Describe how you would design a resilient, fault-tolerant cloud architecture to support a high-traffic microservice migrating from legacy systems.
Approach
- Name the failure you are designing for, then the recovery path.
- Name the read and write paths separately; they rarely have the same bottleneck.
- Fix the scope first: who calls this, how often, and what they do when it fails.
Follow-up
- What breaks first when traffic grows ten times?
- How does this behave when that dependency is down for an hour?
Flight Booking System: Architect a core portion of Hopper's flight boo…
Flight Booking System: Architect a core portion of Hopper's flight booking functionality, detailing how you handle concurrent seat selection and payment processing.
Approach
- State the consistency you need, and where you are willing to be stale.
- Fix the scope first: who calls this, how often, and what they do when it fails.
- Choose a partition key and say what query it makes expensive.
Follow-up
- How does this behave when that dependency is down for an hour?
- What would you drop to keep the system up under load?
Parking Garage Design: Design an object-oriented system for a multi-fl…
Parking Garage Design: Design an object-oriented system for a multi-floor parking garage. Explain your choice of data structures (e.g., Maps vs. Sets) to track open spots and vehicle types.
Approach
- State the consistency you need, and where you are willing to be stale.
- Name the failure you are designing for, then the recovery path.
- Name the read and write paths separately; they rarely have the same bottleneck.
Follow-up
- How does this behave when that dependency is down for an hour?
- What would you drop to keep the system up under load?
Design an amendment endpoint with consent, preconditions and partial failure
The callers are a traveller self-service page and an agent console, both of which retry, while supplier webhooks may cancel the same booking concurrently. An amendment changes dates or party size, must be applied as a per-night delta, and carries a penalty computed from the booking's stored cancellation policy snapshot that the traveller must see and accept before any money moves. Design the endpoint or endpoints: the resource shape, how the penalty is quoted then committed, the precondition that prevents a lost update, the idempotency scope, the states reachable after a partial failure, and what each caller does on every outcome.
Approach
- Model the amendment as its own resource with its own lifecycle — POST /bookings/{id}/amendments — rather than a PATCH on the booking. A PATCH has nowhere to hold a quoted penalty awaiting consent, no identity to retry against, and no state to occupy while the supplier call is unresolved, and the supplier call is precisely the step that will be unresolved.
- Split quote from commit. The first call posts the intended delta and returns an amendment in state quoted, carrying the nights added and removed, the penalty derived from booking.cancellation_policy_snapshot evaluated in the property's IANA zone, the resulting total, and an expiry. The commit names the amendment_id and echoes the quoted amounts; a mismatch is 409 carrying the fresh quote, never a silent reprice. It costs a round trip and some abandonment, and it is the only shape that yields a charge the traveller actually agreed to.
- Stop the lost update with a precondition rather than a read-then-write. The booking carries an ETag over its version, the commit sends If-Match, and a stale tag returns 412 Precondition Failed (RFC 9110). That is what catches both the supplier cancellation that lands between quote and commit and the agent who saves over the traveller's change. Make the header mandatory — a commit without If-Match is 428 Precondition Required (RFC 6585), not a default of 'apply anyway'.
- Scope idempotency to the commit and key it on the amendment_id, not the booking_id, because one booking legitimately has many amendments. Persist the key before any external call and replay the stored response, exactly as the booking path does, so an agent double-clicking cannot produce two penalties.
- Apply the delta as one transaction over nights in ascending stay_date order: release only the dropped nights through a conditional state transition, take only the added nights with a conditional decrement, and leave the overlap untouched. Check every statement's row count before commit. Releasing everything first hands a sold-out night to a concurrent booker and fails the traveller's own amendment; taking everything first double-counts the overlap against the traveller's existing booking.
- Enumerate the partial-failure states instead of hoping they do not arise, because inventory, payment and the supplier commit separately: delta applied but refund unknown, refund succeeded but supplier rejected, supplier accepted but the local write lost. Give the amendment a reconciling state, resolve money and supplier by read-back with the stored keys, and render it honestly — self-service shows pending rather than success, since 'confirmed' is the claim you cannot retract, while the agent console additionally gets the reconciliation detail and a manual path, because an agent can telephone a property and a web page cannot.
Worked solution 40 min
- Draw the amendment state machine — quoted, committing, applied, reconciling, rejected, expired — and mark which transitions a caller can drive.
- Write the commit request and response, including If-Match, the echoed amounts, and the 409 body carrying the replacement quote.
- Write the per-night delta as SQL: conditional release of dropped nights, conditional decrement for added nights, and the row-count assertions that gate the commit.
- Enumerate the three partial-failure states and, for each, what the reconciler reads back and what the two callers see while it runs.
- Work a deliberate stay-shortening across a month boundary and confirm that booking_night rows and the payment adjustment both reconcile to the new total.
Follow-up
- The traveller drops one night, the policy makes that free, and the supplier rejects the modification. What is the booking's state five minutes later, and what did the traveller see in the meantime?
- Two agents quote amendments on the same booking simultaneously and both commit. Walk the second one through your design.
- When would you permit a cancel-then-rebook instead, and what has to be written down for that to be safe?
Shopping API pods are OOMKilled every thirty-six hours
shopping-api pods are OOMKilled at a 4GiB limit roughly every 36 hours. Resident memory climbs close to linearly from 1.2GiB while request rate, result-set size and pod count stay flat. In the final hours p99 degrades and GC CPU climbs, though allocation rate is unchanged. A restart resets everything. A recent change added an in-process cache of connector responses keyed by the trip intent. Deliverable: the ordered checklist that separates a retention bug from ordinary growth, and the reason this key is a leak even if the caching code is correct.
Approach
- Split the question before choosing a tool: plot heap-used-after-collection against resident set size. A rising live set is a retention bug; a flat heap with rising RSS points instead at native buffers, thread stacks, mapped files or allocator fragmentation, which need entirely different instrumentation.
- Diff, do not stare. Take two heap snapshots an hour apart under identical steady load and compare live object counts and retained sizes by type. The leak is the type whose retained size grew by roughly the RSS delta over that hour; everything else is noise and will waste the session.
- Walk the retention path to a garbage-collection root and name the owner: a static map, a thread-local on a pooled worker (which lives as long as the pool, not the request), a listener list, or a metrics registry keyed by a high-cardinality label.
- Count the key space instead of reading the eviction code. The trip-intent key is the product of destination, check-in date, length of stay, party composition, currency, point of sale and promotion eligibility. Against a finite heap that cardinality is effectively unbounded, so an unbounded map over it grows without limit even when every line of the caching logic is correct. It also hides a second defect: a request whose deadline has fired still has connector callbacks in flight, and those callbacks insert after cancellation.
- Fix the grain, not the limit: move the cache to (unit_type_id, stay_date), whose key space is unit types times forward horizon and is therefore bounded and reused across every length of stay and flexibility variant, then add an explicit entry cap with eviction and export the entry count as a metric.
- Prove it rather than declaring it: run the fixed build at the production traffic shape for longer than one previous kill interval and show heap-after-collection flat, not merely growing more slowly.
Follow-up
- GC CPU rose while allocation rate stayed flat. Explain the mechanism, and what it predicts about p99 in the last two hours before the kill.
- With a bounded cache at the correct grain, what hit rate do you expect, and how does eviction interact with a supplier push invalidating a single unit-date?
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 ↗Practice prompt ↗Worked solution ↗Expand any day for tasks and deliverables. Your progress is saved on this device.
Keep one story where the bad call was yours rather than a dependency's or a manager's. Name the check that would have caught it, whether you added that check afterwards, and whether it has fired since. Answers that route blame outward end the conversation early; answers that end in a guardrail someone still relies on tend to open it up.
Describe a time when you had to make a difficult technical decision wi…
Describe a time when you had to make a difficult technical decision with incomplete data. What was the impact?
Approach
- Close with what you would do differently, concretely.
- 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
- What would you do differently if you ran that again?
- How did you know your change caused the improvement?
Give an example of a time when you disagreed with a manager or teammat…
Give an example of a time when you disagreed with a manager or teammate. How did you handle the conflict, and what was the resolution?
Approach
- Name the disagreement and how you resolved it with evidence.
- 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.
Follow-up
- What did you decide not to do, and why?
- What would you do differently if you ran that again?
Resolve a review disagreement over an inventory decrement
A colleague's pull request reads remaining_units from ari_daily, checks it against the requested units in application code, then issues UPDATE ari_daily SET remaining_units = remaining_units - :n for each night. You believe it must be one conditional UPDATE per night with an explicit row-count check, and that the isolation level has to be a stated decision. Describe a code review disagreement of this kind that you had: what you wrote in the comment, what evidence ended it, what you conceded, and what the engine actually does under the isolation level the code assumed.
Approach
- Write the failing interleaving in the comment rather than a principle. Two requests read remaining_units = 1, both pass the application check, both decrement, and the row lands at -1 or is clamped by the CHECK into a constraint error that surfaces as a 500 rather than as sold out. A four-line trace is harder to wave away than 'this is a race'.
- Give the replacement as a statement, not a description: UPDATE ari_daily SET remaining_units = remaining_units - :n WHERE unit_type_id = :u AND stay_date = :d AND remaining_units >= :n, one per night, inside one transaction, with every statement's row count inspected before commit. Say that the CHECK (remaining_units >= 0) is the last line of defence and not the concurrency control.
- State the isolation behaviour precisely for the engine in play, because this is where reviews go in circles. Under PostgreSQL READ COMMITTED a blocked UPDATE re-reads the row version that the blocker committed and re-evaluates its WHERE clause, so the guard still holds and a losing writer gets zero rows. Under REPEATABLE READ or SERIALIZABLE the same statement instead raises a serialisation failure, SQLSTATE 40001, which the caller must catch and retry with a bounded budget. Those are different code paths.
- Raise the second defect while you are there: the nights must be locked in ascending stay_date order. Two multi-night bookings over overlapping ranges that lock in request order deadlock, and that is a separate bug from the lost update.
- End the disagreement with a runnable artefact. A two-connection test that interleaves the two transactions and asserts the second gets zero rows takes twenty minutes and converts the discussion into a red test, which is the only thing that reliably ends this class of argument.
- Concede what is genuinely arguable. Retry budget, whether to hold or to take inventory at a different step, and whether the allowance applies here are judgement calls; the read-then-write is not.
Follow-up
- The author says the window is microseconds and the traffic is low. What is your answer?
- Where exactly do you put the retry for a 40001, and what is the budget before you give up on the booking?
- How would you write the test so it fails reliably in CI rather than one run in fifty?
- 01
Describe a time when you had to make a difficult technical decision with incomplete data. What was the impact?
- 02
Give an example of a time when you disagreed with a manager or teammate. How did you handle the conflict, and what was the resolution?
- 03
A colleague's pull request reads remaining_units from ari_daily, checks it against the requested units in application code, then issues UPDATE ari_daily SET remaining_units = remaining_units - :n for each night. You believe it must be one conditional UPDATE per night with an explicit row-count check, and that the isolation level has to be a stated decision. Describe a code review disagreement of this kind that you had: what you wrote in the comment, what evidence ended it, what you conceded, and what the engine actually does under the isolation level the code assumed.
Is this an official Hopper interview guide?
No. It is PracHub's own research and practice material for the Software Engineer role at Hopper. Rounds and questions reflect what candidates have reported, not a process Hopper has published, and they change over time. Confirm the current format and scope with your recruiter.
PracHub interview research ↗What programming languages are preferred during the technical interviews?
While Hopper's backend stack is primarily Scala, you are generally free to use any language you are most comfortable with (such as Python, Java, or Go) during the coding challenges. However, ensure you are comfortable writing syntactically correct code without heavy reliance on IDE auto-complete, as some interview environments can be restrictive.
PracHub interview research ↗How difficult are the coding assessments compared to other tech companies?
Candidates report that Hopper's technical bar is high, often compared to tier-one tech companies. The questions are typically in the LeetCode Medium to Hard range, with a strong emphasis on practical problem-solving, optimization, and edge-case handling rather than obscure brainteasers.
PracHub interview research ↗How does the "Bar Raiser" interview work?
The Bar Raiser is a final-stage interview conducted by an experienced interviewer from outside the team you are applying to. This round focuses heavily on behavioral alignment, cultural fit, and your long-term potential to elevate the engineering culture at Hopper.
PracHub interview research ↗What is the hybrid or remote work policy for engineers?
Hopper operates with a highly flexible, remote-first philosophy across many of its engineering hubs, including offices in Montreal, Boston, Singapore, and various remote locations globally. Specific expectations depend on the team and geographic location, which should be clarified during your initial recruiter screen.
PracHub interview research ↗Sources & methodology 3 sources ↗
Official role evidence, timestamped platform data and clearly labeled preparation advice.
- 01PracHub interview research ↗
PracHub editorial research into this company and role, maintained with this guide. Candidate-reported, not an employer publication.
platform · Accessed 2026-09-24 - 02PracHub Software Engineer practice ↗
Cross-company practice questions for this role.
platform · Accessed 2026-09-24 - 03PracHub interview preparation framework ↗
The framework the preparation plan follows.
platform · Accessed 2026-09-24