Interview guides · Accor
Accor Software Engineer Interview Questions & Guide 2026
Caching practice for Accor software engineer prep: settle the staleness budget before choosing an invalidation strategy.
The 60-second brief
As a Software Engineer at Accor, you will play a vital role in building the digital infrastructure that powers one of the world’s most prominent hospitality groups. Your work directly impacts the guest experience, from seamless booking engines to the internal platforms that keep global hotel operations running efficiently. You are not just writing code; you are contributing to a massive, international ecosystem where technology bridges the gap between digital convenience and human hospitality.
Scope in the operational half of the job when the seat carries on-call. Rollout, rollback and what you would put on a dashboard belong inside a design answer rather than after it, and an answer that never reaches them reads as someone who has built systems but not run them.
Focus areas
- Decrement per-night inventory with one conditional update
- Treat check-in dates as local civil dates
- Derive idempotency keys from attempts, never from retries
Overview
This role offers a unique intersection of high-scale systems and a focus on user-centric design. Whether you are working on backend services, frontend interfaces, or data-driven applications, you will be expected to balance technical rigor with the company’s commitment to service excellence. The environment is collaborative and global, providing you with the chance to solve complex engineering challenges that influence travel and hospitality experiences for millions of guests across the globe.
The Accor interview process
Accor candidates report 2 rounds · ≈ 2-4 weeks. The stages below are what candidates describe, not a published process.
HR Screening
Before 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
Technical Discussions
Most of the time lost in this format is not lost to thinking. It goes to a standard-library call you half-remember, an off-by-one in a loop bound, and a debugging loop that mutates code at random until something passes. When output is wrong, stop re-reading the whole function: take the smallest input that reproduces it and walk the state through by hand, printing intermediates if the environment allows. Guessing at a fix without a failing case you understand is how a five-minute bug becomes twenty, and the clock does not pause while you do it.
What to demonstrate
- Whether you reach the right structure without a detour, and can write it from memory rather than only recall that one exists
- Whether overflow is considered where the language has fixed-width integers, since a signed 32-bit value stops at 2,147,483,647 and then wraps in Java, is undefined behaviour in C++, and does not arise in Python, whose integers grow instead
- Whether recursion depth is treated as a constraint on large inputs, given that CPython's default limit is 1000 frames and a deep recursion can exhaust the stack in any language where an iterative version would not
- Whether a failing case is isolated and explained before any edit is made to the code
How to prepare
- From an empty file and with no references open, implement the pieces you lean on most: a heap push and pop, an iterative DFS with an explicit stack, and a binary search whose midpoint is written lo + (hi - lo) / 2, which avoids the overflow that (lo + hi) / 2 can hit in a fixed-width integer type
- Time yourself on the ten library calls you look up most, such as sorting with a custom comparator, splitting and joining strings, and finding the next key at or above a value in an ordered map, until the lookup is gone
- Take a solution you know is broken and, before touching it, write one sentence naming the input, the expected value and the actual value. Repeat until you do it without deciding to.
Where candidates lose points
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.
Implementing an amendment as a cancel followed by a rebook
On a sold-out date, releasing the old nights first hands the unit to a concurrent booker and the traveller's own amendment fails, leaving them with nothing; taking the new nights first double-counts the overlap and can fail the capacity check against the traveller's own existing booking. Either ordering also detaches the amendment from its policy snapshot, so the rebooked stay silently acquires today's cancellation terms and today's price rather than the ones that were agreed. Compute the amendment as a per-night delta inside one transaction: release only the nights being dropped, take only the nights being added, leave the overlap untouched, and derive the payment adjustment from the stored policy snapshot rather than from current prices.
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.
Designing for a scale nobody asked for
Ask for request rate, data size, read-to-write ratio and expected growth, then size the simplest option first; one relational instance on current hardware covers a large share of real workloads. Reaching for shards, queues and a cache tier before any number has been quoted reads as pattern-matching rather than judgement.
Questions & practice
Merge k sorted supplier streams under a deadline budget
Medium · heap · k-way merge · top-k · deadline budget
A search fan-out returns K <= 40 supplier result streams, each already sorted ascending by total_minor converted to the display currency using the quote's pinned fx_rate_id. Streams are iterators and advancing one costs a network read. Up to 10,000 results exist across all streams; the page needs the k = 30 cheapest. The request carries a remaining deadline budget, and a stream that misses it is dropped. Return the k cheapest offers plus a completeness flag, minimising elements read. State the cost in heap operations and in stream reads, and state what the flag guarantees.
Approach
- Seed a min-heap with one head element per stream: K entries, heapified in O(K). Do not materialise all 10,000 results — a full merge is O(N log K) with N = 10^4 to produce 30 answers, which is more than two orders of magnitude of wasted network reads.
- Pop the minimum, emit it, then advance only the stream it came from and push its next element. After k pops you hold the k globally cheapest. Elements read is bounded by K + k - 1 = 69, and heap work is k pops at log K, roughly 30 x 5.3 comparisons. Skipping the refill on the final pop is what saves the last read.
- Derive each stream read's timeout from the budget remaining at the instant the read is issued, not from a fixed constant. A fixed per-call timeout lets the last connector spend a budget that earlier connectors already consumed, and the request blows through its deadline while every individual call looks compliant.
- When a stream is dropped, remove it from the heap and set the completeness flag. State the guarantee precisely: the k-th price returned is an upper bound on the true k-th price, because a dropped stream could only have contributed something cheaper. That is a claim a caller can act on; a bare boolean flag is not.
- The sortedness precondition is load-bearing. If the connector contract does not promise it, the shape changes to a bounded max-heap of size k over every result, O(N log k) with no early exit and every element read. Which regime applies is a property of the connector, worth pinning before writing either version.
- Comparison must happen on one currency at one pinned FX rate. Comparing
price_minoracross currencies orders offers by the accident of their ISO 4217 exponent, and re-fetching a live rate mid-merge makes the ordering non-reproducible in a later dispute.
Follow-up
- One stream is consistently slowest but also consistently cheapest. How do you stop the deadline from systematically excluding the best prices?
- Results within a stream are sorted by price, but the page ranks by a blended score. What breaks, and what do you need from the connector to fix it?
- Model the tail: if each of 20 connectors independently exceeds its own p99 one percent of the time, what fraction of requests loses at least one, and does that number justify partial results?
Find stay dates where committed units exceed capacity
Medium · sweep line · difference array · half-open intervals · capacity
For one unit_type_id with known physical_unit_count and oversell_allowance, you are given up to 200,000 confirmed booking rows as (check_in_date, check_out_date, units) where nights are the half-open range [check_in_date, check_out_date), plus active inventory_hold_night rows as (stay_date, units). The horizon is at most 500 consecutive stay dates. Return every stay date on which confirmed booking units plus active hold units exceed physical_unit_count + oversell_allowance, with the committed count and the excess on each. Target O(M + D) time and O(D) space.
Approach
- Build a difference array over the horizon. For each confirmed booking add
+unitsatcheck_in_dateand-unitsatcheck_out_date. Because nights are half-open, the checkout date is where consumption stops rather than a night that is consumed, so the negative delta lands exactly on it with no offset. - Normalise the holds into the same array: each active hold night is a one-night interval, so
+unitsatstay_dateand-unitsat the next civil date. The input arrives at two different grains, and converting both to deltas is what lets a single sweep handle the mixture without a join. - Index the array densely by
stay_date - horizon_startrather than using a sorted map. That makes the pass O(M + D) rather than O(M log M), and at D <= 500 the array is a few kilobytes, so the dense choice is free. - Prefix-sum left to right; the running total at index i is the committed units on that stay date. Compare each against
physical_unit_count + oversell_allowance— the allowance is part of the invariant, not a fudge factor applied afterwards — and emit the breaches with their excess. - Filter the inputs correctly at read time: cancelled bookings and non-active holds are excluded, and so are the hold rows of a booking that has already converted. During the conversion window the same units legitimately exist as both an
activehold and aconfirmedbooking night, and counting both manufactures a breach that is not real. - Deltas that fall outside the horizon still matter. A booking that starts before
horizon_startcontributes to every night inside it, so clamp its+unitsto index 0 instead of dropping the booking, or the first days of the horizon will read low.
Follow-up
- Make this incremental: a single new booking arrives. What do you recompute, and what does that cost compared with the full sweep?
- Run it across 500,000 unit types instead of one. Where does the dense-array choice stop paying, and what would you partition on?
- A breach is found. How do you decide whether it came from a real oversell, a double-counted conversion, or a hold the sweeper failed to expire?
Price every flexible date range without rescanning nights
Hard · prefix sums · incremental window · read amplification · latency budget
One unit type has cached per-night rows over a 43-day slice, each with price_minor, remaining_units, closed_to_arrival, closed_to_departure, stop_sell and min_los. A flexible search asks for 15 candidate check-in dates crossed with lengths of stay 1 through 28, for a party needing one unit, across 5,000 candidate unit types, inside a 300 ms budget. For each length of stay, return the cheapest legal check-in date and its total price. Rescanning the nights of every range is correct; say why it does not fit the budget, and give an approach that does.
Approach
- Quantify the naive cost before optimising it. Rescanning touches the sum of L for L = 1..28, which is 406 nights per check-in, so 15 x 406 = 6,090 night touches per unit type and about 3.0 x 10^7 per request across 5,000 candidates. At the tens of millions of simple operations per second a managed runtime delivers on one core, that is most or all of a 300 ms budget before pricing, ranking and serialisation are paid for.
- Exploit the incremental structure: for a fixed check-in, extending the length of stay by one night adds exactly one night to the running price sum, one candidate to the running minimum of
remaining_units, and one term to the running OR ofstop_sell. Sweep check-in on the outer loop and length of stay on the inner, carrying those three accumulators. - Each (check-in, length) evaluation is then O(1), giving 15 x 28 = 420 night touches per unit type and about 2.1 x 10^6 per request — roughly 14.5x less work — with O(1) extra space and no preprocessing pass at all.
- Apply every restriction at its own night, because their scopes genuinely differ.
closed_to_arrival,min_losandmax_losare read from the arrival row only.stop_sellis the OR over the booked nights[i, i+L).closed_to_departureis read from the row at indexi + L, which is the checkout date and is not a booked night; if that row falls past the end of the cached slice, the range is unevaluable and must be excluded rather than assumed open. - If the access pattern were arbitrary
(i, j)pairs in random order rather than a structured sweep, the incremental trick dies and the right structures are a prefix sum for price (O(D) build, O(1) query) and a sparse table for the range minimum ofremaining_units(O(D log D) build, O(1) query). The sweep beats both here only because the query order is under your control. - The remaining lever is not algorithmic. The 5,000 candidate unit types dominate the cost, so prune before this loop — by destination geometry, by a per-night price floor, or by taking only the top-ranked candidates — and return partial results with an explicit completeness flag when the budget expires anyway.
Worked solution (40 min)
- Build eight cached nights for 2026-06-01 through 2026-06-08 at indices 0-7. Prices 10000, 11000, 9000, 9000, 12000, 12000, 13000, 14000.
remaining_units2, 2, 2, 1, 2, 2, 2, 2. Index 1 has closed_to_arrival true; index 2 has closed_to_departure true and min_los 2; index 5 has stop_sell true. Everything else is false or 1. - Take check-in candidates at indices 0, 1 and 2, lengths of stay 1 through 3, party needing one unit.
- For each check-in, sweep the length of stay upward carrying the running sum, running minimum of
remaining_unitsand running OR ofstop_sell, and mark each range legal or illegal with the reason. - Read
closed_to_departurefrom indexi + Leach time, and note when that index is a night you are not booking. - Collapse to the cheapest legal check-in per length of stay, breaking ties by earliest check-in, and count the night touches against the naive count.
Expected result: Length 1: 2026-06-01 at 10000. Length 2: 2026-06-03 at 18000. Length 3: a tie at 30000 between 2026-06-01 and 2026-06-03, resolved to 2026-06-01. Check-in 2026-06-02 yields nothing at any length. Night touches are 9 incremental against 18 naive on this fixture.
Verify it yourself
- (2026-06-01, length 2) must be rejected, and only because index 2 — the checkout date — is closed to departure. An implementation that checks the last booked night, index 1, wrongly accepts it at 21000 and the length-2 answer changes.
- (2026-06-03, length 3) must be accepted at 30000 despite
stop_sellat index 5, because index 5 is the checkout date and stop-sell applies only to booked nights. ORing over[i, i+L]inclusive wrongly rejects it. - (2026-06-03, length 1) must be rejected by
min_los = 2read from the arrival row; reading min_los from any other night changes which ranges survive. - The minimum
remaining_unitsover the nights of (2026-06-03, length 3) is 1, which still satisfies a party of one — a party of two makes the same range illegal, which is a cheap way to confirm the running minimum is live.
Follow-up
- The party needs two units rather than one. What changes in the accumulators, and does any restriction scope change with it?
- Rank by total price per night instead of total price. Can you prune across lengths of stay now, and what makes that valid?
- The cached slice is per (unit_type, stay_date). What does one supplier push invalidate, and how do you stop a popular date from stampeding the recompute?
Decide whether stay-range availability is derived or precomputed
Hard · denormalisation · read amplification · cache invalidation
Searches outnumber bookings by two to three orders of magnitude. One result page with plus or minus three days of flexibility expands to roughly 10^3 (unit type, range) evaluations over 10^3 to 10^4 rows of ari_daily(unit_type_id, stay_date, remaining_units, restrictions), against a whole-page p99 near one second. Decide whether bookability for a length of stay is derived from the unit-date rows at request time or materialised. Size both options with real numbers over a 450-day horizon and a 30-night maximum stay, and specify the invalidation path for a single supplier push.
Approach
- Size the materialised option before arguing about it. A row per (unit type, start date, length of stay) over a 450-day horizon with stays up to 30 nights is about 13,500 rows per unit type against 450, a thirty-fold increase in a table the ingest path already rewrites millions of times a day. The killer is not the build but the invalidation: a change to one night invalidates every range covering it, which is the sum of one through thirty, or 465 entries per unit type per changed night.
- Size the derived option honestly too. A thousand candidates times up to seven date offsets times four nights is on the order of 10^4 narrow rows per page, served by short index scans on the primary key — a few megabytes of buffer traffic and tens of milliseconds warm. That fits the budget only while those rows are resident, so the real question is the working set and its eviction behaviour, not the row count.
- Choose the grain that composes. Cache at (unit type, stay date), bounded by unit types times horizon, and compose ranges in memory at request time: every entry is reused by every length of stay and every flexibility offset that touches that night, and one supplier push invalidates exactly the unit-dates it wrote, one entry each, with no range expansion at all.
- Reject the request-shaped cache explicitly. Keying on destination, dates, length of stay, party composition, currency, point of sale and promotion eligibility is a cartesian product whose hit rate is near zero outside the head of the distribution, and any input omitted from the key serves one traveller another traveller's price — a class of defect customers report before monitoring notices it.
- Permit one denormalisation for pruning only, and make it conservative. A coarse per-(unit type, month) has-any-availability summary may over-report but must never under-report, used to skip candidates before the exact evaluation runs. A false positive costs one wasted range scan; a false negative hides bookable inventory and is invisible in every metric you currently have.
- State the cost of stale in the currency that matters. Availability served a few minutes old converts into supplier rejections at confirm time, after the card has been authorised, so the freshness target follows from the rejection rate you will tolerate. The measurement that settles the design is rejection rate plotted against the age of the availability behind each offer, not cache hit rate.
Follow-up
- Denormalising listing_status onto ari_daily would make the availability scan index-only. What does delisting one unit type cost then, and whose writes does it contend with?
- A popular date falls out of cache during a flash sale. How do you stop two thousand concurrent searches all recomputing it at once?
- How would you measure whether the per-night cache is genuinely reused across lengths of stay rather than filled once per request and discarded?
Reconcile night revenue against captures without fanning either out
Medium · join fan-out · conditional aggregation · grain mismatch
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.
Expected result: Nights sold and night gross per property per stay month that equal the direct aggregate over booking_night, plus an allocated capture total equal to succeeded captures minus succeeded refunds to the minor unit.
Verify it yourself
- Distinct booking count in the result equals the booking count for the window.
- Removing one payment row changes the capture total and leaves nights sold untouched.
- The month-boundary booking contributes nights to both months and splits its capture in the same proportion.
- A booking with no succeeded capture reports zero rather than NULL.
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?
How do you validate your work, such as through integration tests?
Medium
Approach
- State your assumptions explicitly before working the problem.
- Say what you would check first and why it is the highest-information step.
- Work from the requirement backwards to the design.
Follow-up
- How would you know your answer was wrong?
- What assumption would you test first?
Why did you choose a specific framework (e.g., Spring) for your implementation?
Medium
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?
Design a resumable reservation feed for partner reconciliation
Medium · pagination · cursors · reconciliation
A partner's nightly job pulls every booking changed since its last run to reconcile against its own ledger, while bookings are amended and cancelled continuously during the walk. The current endpoint is GET /bookings?updated_since=&limit=&offset=, and the partner reports both missing and duplicated rows. Specify the replacement: the ordering key, the cursor, the bounds and whether they are inclusive, what happens to a row that changes mid-walk, the delivery guarantee you offer and the obligation it places on the partner, and the answer the partner gets when it resumes from a stale cursor.
Approach
- Retire OFFSET on both counts. The engine must produce and discard n rows to serve page n, so walking N rows costs on the order of N²/limit in total work; and the page is defined relative to a result set that is being mutated, so a row updated mid-walk moves and is either skipped or returned twice. The partner's symptom is a property of the pagination scheme, not of their job.
- Order by a key that is total and stable, and push the comparison into the query: WHERE (updated_at_utc, booking_id) > (:t, :id) ORDER BY updated_at_utc, booking_id LIMIT :n, backed by a btree index on exactly that pair so each page is one index seek plus a short range scan. booking_id breaks ties, and ties are routine because bulk updates share a timestamp.
- Then fix what the tuple does not fix. updated_at_utc is stamped when the statement runs, but the row only becomes visible at commit, so a transaction stamped before your high-water mark that commits after you have read past it is invisible and never reappears. Either trail the watermark by more than the longest write transaction, or order the feed by a sequence assigned at commit — an outbox or change table — which removes the hazard by construction rather than by a timing assumption.
- State the guarantee rather than implying one: at-least-once, ordered by change, with no promise of exactly one delivery per row. That is what makes resumption safe, and it puts a matching obligation on the partner — key the apply on (booking_id, version) and treat a version already seen as a no-op.
- Make the cursor opaque and versioned — base64 of the position plus a schema tag — so the ordering key can change later without breaking stored cursors. Define expiry: a cursor older than the change feed's retention cannot be honoured, so answer 410 Gone carrying the earliest resumable position, rather than silently restarting from the beginning and handing the partner a full re-sync they did not ask for.
- Cap limit server-side and return the page's own high-water mark instead of a total count. A count over a live feed is expensive to compute and already wrong by the time it is serialised.
Worked solution (30 min)
- Write both queries and compare their plans at page 1 and page 10,000 of the same feed.
- Construct the skip concretely: two overlapping write transactions, the slower one committing after the faster one, and identify the row the naive watermark loses.
- Define the cursor's encoded contents, its schema tag, and the response to an expired one.
- State the delivery guarantee and write the partner-side apply rule that makes it safe.
Expected result: A keyset feed ordered either by a commit-assigned sequence or by a lagged (updated_at_utc, booking_id) tuple, with an opaque versioned cursor, at-least-once delivery stated in the contract, 410 plus a resumable position on an expired cursor, and a server-side page cap.
Verify it yourself
- Amending a booking during the walk causes it to be delivered again, never dropped.
- Page latency is flat from the first page to the last rather than growing with position.
- Replaying yesterday's cursor returns the same changes plus the new ones, with no gap at the boundary.
Follow-up
- The partner wants four workers walking in parallel. What do you hand them, and what breaks if they shard on booking_id modulo four?
- A booking is erased after the partner has already ingested it. How does the feed express that?
- How large can one page's payload become, and what do you do about a booking with four hundred nights?
One ingest partition stalls while the rest keep up
Medium · poison message · head-of-line blocking · dead-letter queue
ari-ingest consumer lag is flat on eleven partitions and growing without bound on the twelfth. That consumer's CPU is high, it emits nothing at ERROR level, and the same offset appears in DEBUG output every 40 seconds. Properties hashed to that partition are serving availability hours stale and their confirm-time rejection rate has tripled. Delivery is at-least-once with unlimited retries. Deliverable: the ordered checklist, the disposition for the stuck message, and why skipping it is not sufficient on its own.
Approach
- Establish head-of-line blocking rather than a throughput shortfall: lag growing on exactly one partition beside healthy siblings, with one offset repeating in the logs, is a poison message. Adding consumers cannot help, because a partition is consumed by one member of the group and more members only trigger a rebalance.
- Replay that offset into a staging consumer pointed at a scratch database and let it fail where you can watch it. Read the real failure rather than inferring it: an oversized full-refresh payload exhausting memory mid-apply, a row violating remaining_units >= 0, a supplier_seq regression, an unmapped supplier_unit_code. Each has a different fix and only one of them is a data problem.
- Instrument before changing behaviour: carry a delivery-attempt count on the message and export max attempts per partition, so the next occurrence is an alert with a name rather than a log-reading exercise.
- Bound the retries: after N attempts the message goes to a dead-letter topic with its key, offset, attempt count and last exception, and the offset commits so the partition drains. Alert on every dead-letter write, because a dead-letter queue nobody drains is a silent data-loss channel.
- Repair the data, not only the queue. A full refresh is a set difference over the affected horizon, so dropping one leaves withdrawn dates alive and sellable -- which is precisely the confirm-time rejection rise already visible. Re-request that supplier's refresh for the horizon and verify by diffing ari_daily against the supplier snapshot before calling it closed.
- Make the apply path incremental so one message can no longer be all-or-nothing: chunk the horizon, commit per batch under the monotonic supplier_seq guard, and record progress so a retry resumes rather than restarting.
Follow-up
- A full refresh must behave as a set difference. How do you apply it in chunks without opening a window where a date is withdrawn but the search path can still sell it?
- If the dead-lettered message is replayed after later updates have landed, what does the supplier_seq guard do, and is that the behaviour you want?
Suggested practice checklist
Day one measures instead of guessing, under a fixed rubric, and the remaining hours are allocated in proportion to the gaps before any studying begins. The allocation is deliberately not renegotiated midweek, because the area that feels worst on day three is usually the one that is moving.
Day 1 — Diagnostic, scored before you study anything
- Sit a 110-minute diagnostic in four blocks: forty-five minutes on two coding problems, twenty-five on one design prompt taken to interface and data model, twenty of short-answer fundamentals, and twenty delivering two behavioural answers aloud.
- Score each block from 0 to 3 on a fixed rubric where 3 is correct and fluent, 2 is correct but slow or prompted, 1 is partially correct and 0 is stuck, grading the artifact rather than how the attempt felt.
- Allocate days two to five in proportion to 3 minus each block's score, write the allocation down, and commit to leaving it alone.
Deliverable: A scored rubric and a fixed hour allocation for the rest of the week.
Day 2 — Largest gap: find the boundary rather than the subject
- Split the weakest area into named sub-skills and rate each separately. For coding those are restating the problem, choosing the structure, stating the invariant, turning the invariant into loop bounds, handling empty and single-element input, and accounting for complexity out loud.
- Attempt three items positioned just above where the rating drops off, and for each write the first move you failed to make.
- Re-attempt one of them from blank four hours later with nothing open.
Deliverable: A sub-skill map with the two blocking sub-skills circled.
Day 3 — Drill the blocking sub-skill by repeating the shape
- Do eight short repetitions of the same shape rather than eight different problems, so what gets practised is the pattern and not the puzzle.
- State the rule you now hold in one sentence, then test it against a case built to break it, a sliding window over an array containing negative values, or a cache-aside read path whose invalidation message is dropped.
- Have someone else read your one-sentence rule and find the precondition you left out.
Deliverable: One rule statement with its preconditions attached and one counterexample that would have caught the incomplete version.
Day 4 — Second gap, plus maintenance on the strongest area
- Run the same sub-skill decomposition on the second-largest gap in half the time.
- Spend twenty-five timed minutes on the block you scored highest, choosing the hardest item you can still finish rather than a warm-up.
- Write whether each area fails you on recall, on setup, or on execution, and set the fix accordingly: repetition for recall, a written checklist for setup, timed work for execution.
Deliverable: A second sub-skill map plus a one-line failure diagnosis for each area.
Day 5 — The gap that is not a skill
- Record one technical and one behavioural answer, then count two things in the playback: seconds before your first clarifying question, and sentences you began without knowing where they would end.
- Practise saying that you do not know, followed by how you would find out, without letting it soften into a guess, and practise stating a complexity or an estimate before being asked for it.
- Redeliver one answer under a hard ninety-second cap, which forces structure ahead of detail.
Deliverable: Two recordings with a counted reduction in time-to-first-question.
Day 6 — Retest under day-one conditions
- Sit the same 110-minute structure with new prompts of comparable difficulty and score it on the identical rubric.
- For any block that did not move, change the method rather than adding hours: a block stuck at 1 usually means the practice was too varied, not too short.
- Write down which single block you would still lose the offer on.
Deliverable: A second scored rubric placed beside the first, with one named remaining risk.
Day 7 — Full loop under interview conditions
- Run a sixty-minute mock over the two blocks that moved least, with an interviewer briefed to interrupt and change direction mid-answer.
- Write the recovery script for going blank: restate the question, state your assumption, name the first thing you would check.
- Say every rule from the week aloud without reading it, and cut any you cannot state in a single sentence, since a rule you have to reconstruct mid-answer will not survive an interruption.
Deliverable: A one-page card holding the recovery script and only the rules you could state from memory.
The bug you decided not to fix
Engineers over-index on what they repaired. A stronger answer covers something you knowingly left broken: the alert you tuned down, the data inconsistency you documented instead of chasing, the cleanup you deferred past two quarters. Give the reasoning and the condition that would have reopened it, so it reads as a decision and not as neglect.
What are your professional interests and hobbies?
Medium · behavioural and engineering judgement
Approach
- Name the disagreement and how you resolved it with evidence.
- Close with what you would do differently, concretely.
- Give the blast radius: what could have broken, and what you measured.
Follow-up
- How did you know your change caused the improvement?
- What did you decide not to do, and why?
Tell me about yourself and your career journey.
Medium · behavioural and engineering judgement
Approach
- Give the blast radius: what could have broken, and what you measured.
- Name the disagreement and how you resolved it with evidence.
- Pick a story where you made the decision, not one where you watched it.
Follow-up
- How did you know your change caused the improvement?
- What would you do differently if you ran that again?
Why do you want to work for Accor?
Medium · behavioural and engineering judgement
Approach
- Name the disagreement and how you resolved it with evidence.
- State the situation in two sentences and spend the rest on the reasoning.
- Pick a story where you made the decision, not one where you watched it.
Follow-up
- What did you decide not to do, and why?
- How did you know your change caused the improvement?
How do you approach working in a team environment?
Medium · behavioural and engineering judgement
Approach
- Close with what you would do differently, concretely.
- Name the disagreement and how you resolved it with evidence.
- Pick a story where you made the decision, not one where you watched it.
Follow-up
- How did you know your change caused the improvement?
- What did you decide not to do, and why?