A fast search is useful only if the traveller can trust the result. Work through a journey from search to booking confirmation. Separate a displayed offer, an inventory hold, a payment and an issued ticket: they can fail independently.
Trainline’s official technology overview describes a .NET/C# platform on AWS with microservice patterns, plus React, TypeScript and Node on the web. Match your preparation to the specific team; these broad stack facts do not establish its interview questions or format.
State the clock and currency contract before coding. A departure displayed in local time is not an elapsed duration. A price in minor units is not a floating-point approximation. The exercises use synthetic data, UTC instants and explicit currencies so that you can test boundaries without guessing business rules.
Explain the unhappy path in traveller terms. After a network timeout, can the person safely book again? Which record proves whether a ticket exists? Start with that question before choosing queues, caches or database technology. This is editorial preparation for booking systems, not Trainline’s internal design.
Define the booking contract
editorialFollow a single itinerary. Name the offer identity, its expiry and the currency. A search result is not a guarantee that a later booking attempt will succeed. Explain how your system communicates a changed price before accepting a commitment.
What to demonstrate
- Distinguish cached discovery data from authoritative booking state.
- Make time and amount units explicit.
How to prepare
- Attempt the offer-selection exercise.
- Create an offer expiring exactly when the booking starts.
Isolate unreliable dependencies
editorialA carrier API can be slow, unavailable or ambiguous after accepting a request. Put deadlines around calls and preserve a durable operation identity. Decide which reads may return partial results and which writes require reconciliation.
What to demonstrate
- Explain retry safety separately for reads and writes.
- Keep one unhealthy carrier from consuming all capacity.
How to prepare
- Draw the booking operation state machine.
- List retryable, terminal and unknown outcomes.
Show maintainable engineering
editorialUse small interfaces around time, inventory and payment so you can simulate failures. Dependency injection earns its place when it creates a testable boundary, not when it merely adds layers.
What to demonstrate
- Connect abstraction to a real test or substitution.
- Explain the effect of a release on travellers.
How to prepare
- Reproduce the inventory race with two buyers.
- Prepare an example of improving an API without breaking its callers.
PracHub editorial advice for the preparation topics above.
Treating a timeout as a rejected booking
Keep the outcome unknown until a durable record or reconciliation resolves it. A new request key can create an unintended second booking.
Summing money after a one-to-many join
Payment attempts multiply ticket rows. Aggregate value at the ticket identity before combining it with operational metrics.
Comparing local time strings
Convert explicit timezone-aware instants before ordering. State how ambiguous timetable inputs are resolved.
Retrying without a budget
Bound time, attempts and concurrency. Writes need an operation identity and a known retry contract, not just exponential backoff.
Choose a category, try a prompt, then open its approach, worked solution or follow-up when you need it.
Choose the cheapest valid offer per journey
Given offers with journey_id, offer_id, currency, price_minor and expires_at, select one per journey in a requested currency at UTC instant now. Expiry is exclusive; break equal-price ties by offer_id. Reject negative or noninteger prices.
Approach
- Validate the record contract, then discard the wrong currency and any offer with expires_at <= now.
- Track the best (price_minor, offer_id) tuple per journey. Return results in journey-ID order. Explain O(n + j log j) time and O(j) retained state.
Worked solution 35 min
- Validate monetary types before comparing prices. Boolean values are rejected even though Python treats them as integers.
- Ignore expired offers and currency mismatches. Choose by a deterministic tuple so input order cannot change the result.
- This selects a candidate offer; booking still needs authoritative validation.
def choose_offers(offers, currency, now):
best = {}
for o in offers:
price = o["price_minor"]
if type(price) is not int or price < 0:
raise ValueError("nonnegative integer minor units required")
if o["currency"] != currency or o["expires_at"] <= now:
continue
key = o["journey_id"]
if key not in best or (price, o["offer_id"]) < (best[key]["price_minor"], best[key]["offer_id"]):
best[key] = o
return [best[k] for k in sorted(best)]
Scroll sideways to view long lines.
Follow-up
- How would stale availability affect the decision to book the selected offer?
Validate a chain of connections
Given ordered legs with origin, destination, departure and arrival as UTC seconds, check that places connect and each transfer leaves at least the required minimum connection time. Arrival cannot precede departure.
Approach
- Validate each leg independently, then compare each arrival with the next departure. Equality at the required minimum is valid.
- Return the first failing leg or connection with a useful reason. Treat an empty itinerary according to an explicit caller contract.
Follow-up
- How would daylight-saving changes enter when converting local timetable values to UTC?
Count recent carrier responses
Given nondecreasing integer timestamps, count events in (now − 10, now]. Each event counts once. The clock can advance without a new event; an event at the lower boundary has expired.
Which events still count?
The left boundary is excluded; the right boundary is included. Drag past an event to see it enter, then expire 10 seconds later.
See the event values
- At 0s: +1 — expired
- At 5s: +1 — in window
- At 10s: +1 — in window
- At 14s: +1 — not arrived
- At 19s: +1 — not arrived
Synthetic carrier responses at 0, 5, 10, 14 and 19 seconds, each with weight one. Drag the clock: at 10 seconds only 5 and 10 count; at 30 seconds none remain. The lower boundary is excluded.
Approach
- Keep a deque and remove timestamps at or before now − 10 before reporting a count. Equal timestamps represent distinct events unless a separate event ID says otherwise.
- Expose both add(time) and count(now). Reject a backward clock and document the O(k) retained-event memory cost. Each event is inserted and removed once, giving amortized O(1) updates.
Follow-up
- How would late arrivals, multiple producers or a per-customer limit change the contract?
Report confirmed ticket value without attempt inflation
For each carrier, total confirmed ticket value in one currency before a cutoff. A booking can have many payment attempts but at most one confirmed ticket record. Include carriers with zero tickets.
Approach
- Aggregate ticket records at their own identity before joining to carriers. Do not join payment attempts into the value calculation.
- Filter status, currency and time explicitly. Use a left join and COALESCE so empty carriers remain visible.
Worked solution 35 min
- Keep ticket value separate from payment-attempt counts.
- Filter confirmed tickets in the reporting currency, using an exclusive UTC cutoff.
- Aggregate first and preserve the carrier list through a left join.
CREATE TABLE carriers (id TEXT PRIMARY KEY);
CREATE TABLE tickets (id TEXT PRIMARY KEY, carrier TEXT, currency TEXT, minor INTEGER, status TEXT, at TEXT);
CREATE TABLE payment_attempts (ticket_id TEXT, attempt INTEGER);
INSERT INTO carriers VALUES ('c1'),('c2');
INSERT INTO tickets VALUES ('t1','c1','EUR',2500,'confirmed','2026-09-01T10:00:00Z'),
('t2','c1','GBP',900,'confirmed','2026-09-01T10:00:00Z'),
('t3','c2','EUR',800,'pending','2026-09-01T10:00:00Z');
INSERT INTO payment_attempts VALUES ('t1',1),('t1',2);
WITH totals AS (
SELECT carrier,SUM(minor) AS value FROM tickets
WHERE status='confirmed' AND currency='EUR' AND at<'2026-09-02T00:00:00Z'
GROUP BY carrier
)
SELECT c.id,COALESCE(t.value,0) FROM carriers c
LEFT JOIN totals t ON t.carrier=c.id ORDER BY c.id;
Scroll sideways to view long lines.
Follow-up
- How would partial refunds change the data model and the report name?
Find operations still awaiting confirmation
Given operations and status_events, select the latest status per operation as of a cutoff, then return only pending or unknown states. Multiple events can share a timestamp; event_seq is a unique increasing tie-breaker.
Approach
- Filter to events before the cutoff before ranking. Otherwise a future confirmation can erase a historically pending operation.
- Rank by occurred_at descending and event_seq descending. State how an operation with no status events is represented.
Follow-up
- Which index supports both operation lookup and the ordering?
Design a booking flow with an uncertain carrier outcome
Accept one booking intent, coordinate payment and carrier confirmation, and survive a lost response. Assume the carrier does not promise idempotent writes. State how the user checks the outcome.
Approach
- Persist the intent and request fingerprint under a unique client key. Maintain explicit payment and ticket states rather than one success boolean.
- Reconcile an uncertain carrier result using its reference or support process before redispatch. If payment succeeded but a ticket is definitively unavailable, use an auditable compensation path.
Worked solution 35 min
- Authorize the request and persist an immutable booking intent plus client key. Reject the same key with different itinerary or price details.
- Coordinate payment and ticket states explicitly. A successful network response is evidence only for the operation it identifies.
- If dispatch times out after the carrier may have acted, retain unknown status and reconcile. Without a deduplication guarantee, automatic redispatch can create two bookings.
- Expose a stable status URL. Define terminal failure, confirmed ticket and compensation as separate outcomes; record the evidence behind each transition.
Follow-up
- What changes when the carrier supports an idempotency key or an expiring inventory hold?
Design resilient multi-carrier search
Query several carriers within a bounded latency budget. Return useful partial results while making freshness clear. Keep one slow carrier from exhausting the whole service.
Approach
- Allocate per-call deadlines and a total request deadline, with bounded fan-out. Separate cached offers from live availability and retain source timestamps.
- Use independent capacity limits per carrier, observe timeouts and handle cancellation. Explain how you measure the trade-off between result completeness and response time.
Follow-up
- Which retry budget prevents a degraded carrier receiving more traffic precisely when it is least healthy?
Prevent two buyers taking the final place
Two requests read remaining=1 and each write remaining=0 after accepting a booking. Explain why both succeeded and repair the local reservation step.
Approach
- Reproduce two reads before either write. Put remaining > 0 in the atomic update and check affected rows.
- Commit the reservation identity in the same transaction. The local invariant does not guarantee external carrier inventory; keep that boundary explicit.
Worked solution 35 min
- Move the availability predicate into the database statement.
- Allow reservation creation only after one row changes, and commit both operations in a transaction.
- This fixture proves the predicate sequentially. A real concurrency test must exercise the selected database isolation and transaction implementation.
CREATE TABLE inventory (journey TEXT PRIMARY KEY, remaining INTEGER CHECK(remaining>=0));
INSERT INTO inventory VALUES ('j1',1);
UPDATE inventory SET remaining=remaining-1 WHERE journey='j1' AND remaining>0;
SELECT changes();
UPDATE inventory SET remaining=remaining-1 WHERE journey='j1' AND remaining>0;
SELECT changes();
SELECT remaining FROM inventory;
Scroll sideways to view long lines.
Follow-up
- How do expiry, cancellation and duplicate release messages affect reservation counts?
A PracHub practice schedule with one outcome per session. Adjust the pace to your experience and interview date; it is not a company hiring timeline.
Prepare, practise & reflect
One practical outcome each day. Spend longer where you need it.
0 / 7 done01Trace a traveller journey
- Draw search, offer, booking, payment and ticket states.
- Mark where authority moves to an external service.
Deliverable: A state map
02Choose offers correctly
- Implement offer selection.
- Test ties, currency and expiry.
Deliverable: A tested selection function
Practice prompt ↗03Verify reporting grain
- Run the ticket-value fixture.
- Add payment attempts and prove no total inflation.
Deliverable: A stable SQL report
Practice prompt ↗04Design the uncertain outcome
- Walk the booking flow through a lost carrier response.
- Define a reconciliation action and the user-visible status.
Deliverable: A recovery contract
Practice prompt ↗05Reproduce the last-place race
- Run the conditional update fixture.
- Explain the transaction needed for reservation identity.
Deliverable: A concurrency timeline
Practice prompt ↗06Practise resilient integration
- Sketch deadlines and carrier capacity limits.
- Prepare an API-change story.
Deliverable: An integration review sheet
Practice prompt ↗Practice prompt ↗07Review aloud
- Solve connection validation and inspect the event clock.
- Explain one unresolved limitation in your design.
Deliverable: A focused next-practice list
Practice prompt ↗Practice prompt ↗Expand any day for tasks and deliverables. Your progress is saved on this device.
Use a real project. Explain your responsibility, the decision you made, the evidence you used and what you would change.
Evolve an integration safely
Describe an API change where you protected existing callers while introducing a better contract.
Approach
- State the constraint and your decision, including what another reasonable engineer might have chosen.
- Describe how you verified the result and kept affected people informed. Use actual evidence from your own work.
Follow-up
- What would you monitor to know the decision should be revisited?
Negotiate a safer release scope
Explain a deadline decision where you narrowed a feature to preserve correctness.
Approach
- State the constraint and your decision, including what another reasonable engineer might have chosen.
- Describe how you verified the result and kept affected people informed. Use actual evidence from your own work.
Follow-up
- What would you monitor to know the decision should be revisited?
Communicate during an outage
Describe how you explained a dependency outage and uncertainty to a nontechnical partner.
Approach
- State the constraint and your decision, including what another reasonable engineer might have chosen.
- Describe how you verified the result and kept affected people informed. Use actual evidence from your own work.
Follow-up
- What would you monitor to know the decision should be revisited?
- 01
Choose examples you can discuss without sharing confidential customer data.
Which language should I prioritize?
The official teams page describes .NET/C# on the platform and React, TypeScript and Node on the web. Prioritize the exact role. Python here is only the reference language for a small algorithm.
Trainline — official engineering and product context ↗Are these verified company interview questions?
These are PracHub practice exercises informed by the supplied guide themes and official product context. They include original constraints and worked solutions; they are not an independently verified list of questions asked by the employer.
Why include SQL alongside coding and design?
SQL is supplemental practice for inspecting system state and checking invariants. Its inclusion does not mean every role has a SQL interview. Prioritize the skills in your exact opening.
How should I use the seven-day checklist?
Attempt each task before opening its solution. Save one artifact per session, such as a tested function, fixture or failure timeline. Repeat weak areas and adjust the pace instead of treating seven days as a readiness guarantee.
Sources & methodology 3 sources ↗
Official role evidence, timestamped platform data and clearly labeled preparation advice.
- 01Trainline — official engineering and product context ↗
Company context only; preparation recommendations are PracHub editorial advice.
official · Accessed 2026-09-20 - 02PostgreSQL — Window functions ↗
Technical reference for SQL reasoning. Runnable teaching fixtures below use SQLite.
official · Accessed 2026-09-20 - 03PracHub — Software Engineer practice ↗
Cross-company practice; not evidence of company interview questions.
platform · Accessed 2026-09-20