Fora Travel builds web software for travel advisors and aims to replace the legacy industry tools advisors have used to book trips, manage bookings and work with clients. Software Engineers build those products across the stack: client-facing web applications and UI components in JavaScript and CSS, REST APIs for hierarchical content, live notifications, and location-aware backend services.
The reported interview questions cover the same ground. The reported coding questions are array fundamentals. The main reported problem is merging an unsorted array into the empty tail of another, sorting the result, and stating its time and space complexity. Design questions cover a three-level REST API (Course, Module, Lesson) and a mobile backend that turns periodic location reports into proximity notifications. A frontend debugging exercise asks you to fix CSS layout errors and JavaScript runtime bugs in a broken application.
The behavioural questions reported for the role ask why you want to build tools for the travel industry, when you had to explain a technical decision to a peer or hiring manager, and how you use AI productivity tools in your daily work. For each one, prepare a specific answer that holds up to a follow-up question. A named decision, a named tool or a named advisor problem gives the interviewer far more to work with than general enthusiasm.
Recruiter Screen
reportedCandidates describe the recruiter screen as a conversation about career history, salary expectations and interest in travel technology. The reported question bank also includes a question that asks directly about salary expectations and interest in travel. Take both topics seriously. A vague range or a generic love of travel leaves the recruiter nothing concrete to pass on, while a clear range and a specific reason to build software for travel advisors move the process forward cleanly.
What to demonstrate
- How clearly you summarise your career history and connect it to full-stack web work
- Whether you give a specific salary range instead of deflecting the question
- Whether your interest in travel technology is tied to the advisor's work rather than to your own trips
How to prepare
- Write a short career summary that ends on the work closest to this role: web interfaces, REST APIs, notifications or location data
- Set your range from a few current data points for the level and location, and practise saying it plainly in one sentence
- Prepare one specific reason to build software for travel advisors, such as managing bookings, client communication or replacing legacy tools, and a question of your own about the product
Technical Evaluations
reportedCandidates report that this stage covers live coding, frontend debugging and architectural design, including API hierarchy and geospatial notifications. The reported coding questions are about arrays. One asks you to merge an unsorted array b into the empty tail of array a, sort the result and then state its time and space complexity. Another asks for the first element that meets a filter or merge condition. The debugging exercise asks you to fix CSS layout errors and JavaScript runtime bugs in a broken frontend. The design questions include REST APIs for a Course, Module and Lesson hierarchy, and a mobile backend where clients periodically report their location to trigger proximity-based notifications.
What to demonstrate
- In-place array handling that respects the reserved tail and edge cases such as an empty b, plus an exact time and space complexity for the approach you chose
- A methodical debugging process in plain CSS and JavaScript: reproduce the bug, isolate it, fix it, then verify the fix
- Clean resource modelling, request and response contracts, authentication and consistent error handling across nested REST endpoints
- A geospatial design that separates location ingestion from notification processing and indexes locations for proximity queries
How to prepare
- Solve the merge two ways: copy b in and then sort, and sort both parts and merge backwards from the end of a. State the complexity and extra space of each out loud.
- Debug a deliberately broken page with browser devtools. Include a flex or grid misalignment, a box-model overflow and an unhandled promise rejection.
- Design the Course -> Module -> Lesson endpoints end to end: URIs, verbs, payloads, pagination, authorization checks at each level and one error format
- Sketch the proximity notification backend with a named spatial index (geohash, S2 or quadtree), an ingestion queue and push delivery that does not send duplicates
Panel Review
reportedCandidates describe a final panel with a Hiring Manager and senior leaders that assesses execution and alignment. It may be held in the office or as a video panel. The reported behavioural questions are not tied to a specific stage, so have them ready for this conversation as well as earlier ones. Those questions ask about explaining a technical decision, using AI tools day to day and why you want to work in travel technology. Be ready to revisit projects you described earlier. The scope, trade-offs and results you quote here should match what you said in the technical conversations.
What to demonstrate
- Evidence of execution: projects where you carried work from requirements to production and can name your part in it
- Alignment with the role and with building software for travel advisors
- Consistency between how you describe a project here and how you described it in earlier stages
How to prepare
- Choose two projects and write down the facts you will quote for each: scope, your role, the main trade-off and the result. Rehearse until they come out the same every time.
- Prepare the reported behavioural answers: a technical decision you explained to a peer or manager, how you use AI tools and check their output, and why you want to build software for travel advisors
- Prepare questions for the Hiring Manager about the team's product area and how engineers work with product and design
1 candidate reports. Individual accounts describe a particular role and hiring cycle.
Fora Travel Software Engineer Interview Experience — Coding, System Design, and API Design in One Technical Screen
Coding The question was pretty simple: Q: Given two unsorted integer arrays a and b, where a has enough placeholder positions at the end to hold all elements of b, merge b into a and sort the final array in ascending order. Example: Additional constraints: Both a and b are unsorted a has enough placeholder positions at the end to hold b The final result needs to be placed in a There was no explic…
Read full experiencePracHub editorial advice for the preparation topics above.
Treating the array merge as concatenate-and-sort without using a's reserved tail or stating the space cost
The reported prompt gives you array a with empty slots at the end, sized to hold b. Write b into those slots by index instead of building a new array, then sort a in place. Then state the cost: O((m + n) log(m + n)) time, with extra space set by the sort you use (heapsort needs O(1), Timsort up to O(n)). If you are asked to improve it, sort b and the filled prefix of a, then merge backwards from the last slot so no value is overwritten before it is read. Test an empty b, an empty prefix in a, and duplicates that appear in both arrays.
Rewriting or restyling the broken frontend instead of isolating each bug
Reproduce each bug before changing code, then narrow it down. For layout errors, inspect the computed box model and the flex or grid container. For runtime errors, read the console and the stack trace. Fix one bug at a time, say what caused it, and confirm the fix at the viewport or interaction where it broke. A broad workaround hides whether you understand box-sizing, stacking contexts or promise rejection, so name the concept behind each fix.
Nesting every Course, Module and Lesson route three levels deep with no shared auth or error model
Nest routes only where the parent scopes a collection, for example POST /courses/{courseId}/modules or GET /modules/{moduleId}/lessons. Address individual resources by their own id, such as GET /lessons/{lessonId}, so reading or updating a lesson does not need redundant parent ids. Check authorization against the resource's parent chain, looked up from the stored record rather than taken from the path. If you do expose a fully nested route such as /modules/{moduleId}/lessons/{lessonId}, return 404 when that lesson does not belong to that module. Use one error body with a machine-readable code on every endpoint, and add pagination and filtering to the list endpoints.
Designing proximity notifications as one synchronous path from location report to push
Separate ingestion from processing. The write path for periodic location reports should only validate and enqueue. Proximity matching then runs asynchronously against a spatial index such as geohash prefixes, S2 cells or a quadtree. Explain how reporting frequency affects write volume and device battery. Explain how you avoid sending the same notification twice when repeated reports land inside the same geofence, and how you retry or record a push that fails to deliver.
Answering the salary and why-travel questions with generalities
Candidates report that both come up in the recruiter screen. Give a range based on current data for the level and location, and say it once, clearly. For motivation, name a problem in the advisor's work, such as managing bookings, communicating with clients or working around legacy tools, and connect it to something you have built. Liking travel yourself is not a reason the recruiter can pass on.
Choose a category, try a prompt, then open its approach, worked solution or follow-up when you need it.
Given two unsorted integer arrays `a` and `b`, where `a` has enough pl…
Given two unsorted integer arrays a and b, where a has enough placeholder positions at the end to hold all elements of b, merge b into a and sort the final array in ascending order.
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
- What is the worst case, and how likely is it on real data?
- How does this change if the input no longer fits in memory?
What is the time complexity and space complexity of your array merging…
What is the time complexity and space complexity of your array merging and sorting approach?
Approach
- Restate the input: its shape, its size, and what is guaranteed about it.
- State the target complexity and say which constraint rules the naive version out.
- Name the brute-force solution and its complexity before improving on it.
Follow-up
- How does this change if the input no longer fits in memory?
- What is the worst case, and how likely is it on real data?
Given an unsorted array, return the first element after applying a spe…
Given an unsorted array, return the first element after applying a specific filter or merge condition.
Approach
- Walk one small example through your approach before writing the whole thing.
- Restate the input: its shape, its size, and what is guaranteed about it.
- Name the brute-force solution and its complexity before improving on it.
Follow-up
- How does this change if the input no longer fits in memory?
- What is the worst case, and how likely is it on real data?
Find stay dates where committed units exceed 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.
Worked solution 25 min
- Set
physical_unit_count = 3andoversell_allowance = 1, so the ceiling is 4. Horizon 2026-03-01 through 2026-03-06. - Take two confirmed bookings: Bk1 check_in 2026-03-01, check_out 2026-03-04, units 2; Bk2 check_in 2026-03-03, check_out 2026-03-05, units 3. Take one active hold night: 2026-03-03, units 1.
- Write the deltas: +2 at 03-01, -2 at 03-04, +3 at 03-03, -3 at 03-05, +1 at 03-03, -1 at 03-04.
- Prefix-sum across the six dates and list the committed count per date.
- Redo the same fixture with the closing delta wrongly placed at
check_out_date + 1and compare the two breach lists.
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?
Decide whether stay-range availability is derived or precomputed
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.
Worked solution 40 min
- Compute rows per unit type for both designs over 450 days and stays of one to thirty nights, and the invalidation count for a single night's change in each.
- Measure the derived path end to end with a warm cache and a cold one, recording buffer counts from the analysed plan.
- Replay one day of real search shapes against a per-night cache and against a request-key cache and compare hit rates on identical traffic.
- Plot confirm-time rejection rate against the age of the availability that produced each offer.
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?
Delist a unit type without deleting its booking history
unit_type(unit_type_id, property_id, supplier_id, supplier_unit_code, listing_status in ('active','paused','stop_sell','delisted','suspended_quality'), delisted_at_utc, created_at_utc) carries UNIQUE (supplier_id, supplier_unit_code), and booking.unit_type_id references it. A supplier withdraws a unit type, then six weeks later re-publishes the same supplier_unit_code as a different room. Give the DDL that lets both rows coexist while keeping exactly one live row per supplier code, and that keeps search off the dead ones. Then write the monthly bookings-per-property query and say where the listing_status predicate goes so bookings on delisted units still appear.
Approach
- Rule out the hard delete twice over: the foreign key from booking has no cascade you would want, since deleting a unit type would destroy or orphan paid reservations, and the row is evidence — the rate and cancellation terms that were agreed only make sense against it. So delisted_at_utc is the instant and listing_status is the reason, and neither is optional.
- Move the uniqueness to the live rows only: CREATE UNIQUE INDEX ux_live_supplier_unit ON unit_type (supplier_id, supplier_unit_code) WHERE delisted_at_utc IS NULL. One live row per supplier code, unlimited dead ones, and the re-publish six weeks later inserts cleanly.
- Reject the three-column trick explicitly. UNIQUE (supplier_id, supplier_unit_code, delisted_at_utc) does not work in PostgreSQL, because NULLs compare as distinct and two live rows both carrying NULL are both accepted. PostgreSQL 15's UNIQUE NULLS NOT DISTINCT changes that, so it is a version precondition to state rather than assume.
- Make the search filter enforceable instead of remembered. A partial index on (property_id) WHERE listing_status = 'active' is usable only when the planner can prove the query predicate implies the index predicate: listing_status = 'active' does, listing_status <> 'delisted' does not, and the second form silently falls back to a sequential scan. A view over the live rows with select revoked on the base table removes the discipline problem entirely.
- Put the predicate in the right clause for reporting. LEFT JOIN booking to unit_type with ut.listing_status = 'active' in the WHERE clause turns the outer join into an inner join and drops every booking on a since-delisted unit from the month's revenue — precisely the rows finance will ask about. It belongs in ON, or nowhere, because the report is about bookings rather than about current sellability.
- Keep the two states separate. listing_status describes present sellability and paused or stop_sell are reversible; delisted_at_utc is terminal. Collapsing them into one nullable column makes a paused unit indistinguishable from a dead one to the job that decides which ARI rows are still worth maintaining.
Follow-up
- The re-published code turns out to be the same physical room under a new name. How would you discover that, and what breaks if you merge the two rows after bookings exist against both?
- The supplier keeps pushing ARI rows for the delisted unit. Do you keep ingesting them, and what does that answer cost in table size over a 450-day horizon?
- Someone asks what the unit was called on the day a booking was made. Which of your tables can answer that, and which cannot?
Design a set of REST APIs for a three-level content structure (e.g., C…
Design a set of REST APIs for a three-level content structure (e.g., Course -> Module -> Lesson).
Approach
- Design the error taxonomy before the success shape; callers branch on it.
- Say who the caller is and what they do when the call fails halfway.
- State how the contract changes without breaking existing clients.
Follow-up
- What happens if the caller retries after a timeout?
- How does a client discover it is on an old version of this contract?
Given a buggy frontend application, identify and resolve layout render…
Given a buggy frontend application, identify and resolve layout rendering errors in CSS and runtime bugs in JavaScript.
Approach
- Say what you would check first and why it is the highest-information step.
- State your assumptions explicitly before working the problem.
- Clarify what is being asked and what a complete answer contains.
Follow-up
- What assumption would you test first?
- How would you know your answer was wrong?
Design a mobile backend system where client applications periodically …
Design a mobile backend system where client applications periodically report location data to trigger proximity-based notifications.
Approach
- Work from the requirement backwards to the design.
- State your assumptions explicitly before working the problem.
- Say what you would check first and why it is the highest-information step.
Follow-up
- How would you know your answer was wrong?
- What assumption would you test first?
Classify booking-confirm errors for a client that retries blindly
The caller is a partner's server-side integration whose HTTP layer retries any non-2xx up to three times. Design the error contract for POST /bookings. Cover malformed input, an expired quote, a quote whose price no longer matches, no remaining units on one night of the range, an expired hold, a declined card, a supplier that timed out, and throttling. Give the media type, the fields every error carries, and for each class the status code, a stable machine code, whether re-sending is safe, and what the partner's system does next.
Approach
- Fix the envelope first: application/problem+json (RFC 9457, which obsoletes RFC 7807) carrying type, title, status, detail and instance, plus two extension members — an enumerated machine code and a retry class of never, after_backoff or resend_same_key. The partner branches on the code; the prose stays free to change without breaking anyone.
- Partition by two questions: is the server's resulting state known, and can the caller change the outcome. Known and conclusive gives 409 or 410 with distinct codes for quote_expired, price_changed and hold_expired, where price_changed carries the replacement quote so the partner can re-consent without searching again. No_availability names the specific stay_date, because 'some night in your range' is not something a caller can act on.
- A card decline is a definite answer about money, not a server error. 402 is formally reserved in RFC 9110 but is the de-facto code for a refused instrument; 422 with the decline reason is equally defensible. What matters is that it is not 5xx and that retry=never is machine-readable, because mapping a decline to 500 is how a partner re-presents a card the issuer just refused.
- The supplier timeout is the only genuinely unknown class. Either answer 202 with the booking in reconciling plus a poll-or-webhook contract, or return a status that explicitly carries retry=resend_same_key. Either way the partner must re-send with the same idempotency key rather than mint a new one, and that instruction belongs in the taxonomy rather than in prose three pages away.
- Throttling is 429 with Retry-After (RFC 9110 §10.2.3), the one retry hint every HTTP client already implements. Then pin the set with a test asserting every error-producing path emits a code from the published list: without it the framework's catch-all returns an uncoded 500 for a definite refusal, and a blind-retry client answers that with three more attempts against live inventory.
Worked solution 25 min
- Lay the eight classes out as rows and fill four columns: status, machine code, retry class, partner action.
- For each row, answer whether the server's state is known afterwards; any row where it is not belongs in the unknown class rather than in a 5xx.
- Write the full problem+json body for three of them, including the extension members and the replacement quote for price_changed.
- Add a test that enumerates every error path and asserts the emitted code is a member of the published set.
Follow-up
- The partner ignores your retry class and re-sends after a 402. What inside the system stops a second authorisation?
- How do you add a new code without breaking a partner whose client switches exhaustively over today's set?
- no_availability names the night. Is that disclosing another traveller's activity, and does that change your answer?
Availability cache collapses minutes after every rate push
Every few minutes search p99 jumps from 800ms to 6s for about 20 seconds and read-replica CPU saturates, then recovers with no deploy and no error spike. The availability cache holds one entry per (unit_type_id, stay_date) with a 60-second TTL written at fill time. The spikes correlate with ari-ingest applying supplier pushes. During a spike the miss count is far larger than the number of distinct keys missing. Deliverable: the ordered checklist that identifies the mechanism, and a fix that does not raise confirm-time rejections.
Approach
- Align timelines before changing anything: overlay spike onset against ari-ingest apply receipts and against cache eviction counts. Correlation with pushes points at invalidation; a spike a fixed number of seconds after a fill or a warmup points at synchronised expiry. Both mechanisms can be present at once and they have different fixes.
- Use the stated clue rather than guessing: misses far exceeding the count of distinct missing keys means many requests are recomputing the same key concurrently. That is duplicated work, not insufficient capacity, which is why adding cache nodes changes nothing.
- Check the TTL distribution. Entries filled together by one warmup or one bulk push expire together; multiply each TTL by a uniform jitter factor so expiry de-correlates and the recompute load becomes a plateau rather than a pulse.
- Add single-flight per key: the first miss computes while concurrent misses on that key wait on the same in-flight computation. That caps recompute concurrency at one per key per process, so across M processes the replica sees M, which is the number you can actually size for.
- Choose a staleness budget deliberately instead of raising the TTL. Serve-stale-while-revalidate is safe only inside the window the confirm path can absorb, because stale availability does not surface as a cache miss -- it surfaces as a supplier rejection after the traveller has been charged.
- Make invalidation precise: one push touches specific (unit_type_id, stay_date) rows, so publish exactly those keys rather than flushing a property or a date prefix. After the fix, a push should produce one recompute per affected key, not a page-wide stall.
Follow-up
- What does a 30-second stale window cost in confirm-time rejection rate, and how would you measure that rather than estimate it?
- Single-flight is per process. What is the worst-case concurrent recompute across the fleet when a popular property's full horizon is refreshed, and does it still fit the replica's budget?
For someone who has spent the last few years shipping features and reading other people's code, and who has not solved a timed problem from a blank file in a long time. Five days rebuild the primitives and the patterns that sit on them, working from invariants rather than remembered solutions, and the last two attach that back to the rest of the loop.
Prepare, practise & reflect
One practical outcome each day. Spend longer where you need it.
0 / 7 done01Array merge into a reserved tail, with complexity
- Solve the reported merge: write b into a's empty tail by index, then sort a in place
- Solve it again by sorting b and the filled prefix of a, then merging backwards from the last index of a
- For both versions, write the time complexity and the extra space, and name the sort you relied on
- Solve merge-two-sorted-arrays with a two-pointer scan, then test an empty b, an empty prefix, all-equal values and negatives
Deliverable: Two working merge solutions, each with a one-line complexity statement and a passing list of edge cases.
Practice prompt ↗Practice prompt ↗Worked solution ↗02First-element queries and linear array sweeps
- Put the reported first-element question in a concrete form: merge two arrays and return the first element that appears exactly once, using a count map and an order-preserving scan
- Normalise an array of API values that may contain nulls, strings or unexpected types, keeping only valid numbers, and state how each bad type is handled
- Write duplicate detection for an array two ways in code, once by sorting and scanning neighbours and once with a hash set, and state the time and extra space of each
- Work the capacity-breach worked exercise (drill-coding-3) by hand on its fixture and check the committed counts it lists
Deliverable: Three solved array problems with complexity written for each, and the drill-coding-3 fixture reproduced correctly.
Practice prompt ↗Practice prompt ↗03Frontend debugging in plain CSS and JavaScript
- Break a small page on purpose with an overflowing flex item, a content-box width wider than its container and a z-index trapped in a new stacking context. Fix each one with devtools and write one line on its cause.
- Write and then fix three async bugs: an unawaited promise, an unhandled rejection and a stale closure in an event handler. Explain how promises and async/await relate.
- Review React state versus props, and outline how you would keep a large data table responsive with stable keys, memoisation and virtualised rows
Deliverable: A bug log with six entries, each giving the symptom, the cause and the fix.
Practice prompt ↗Practice prompt ↗04REST APIs for Course, Module and Lesson
- Design the full endpoint set: URIs, verbs, request payloads, query parameters for pagination and filtering, and response shapes
- Add authentication, show how the caller's identity reaches each handler, and add an authorization check that walks the parent chain
- Work the booking-confirm error-contract worked exercise (drill-design-4), then apply the same error envelope to the course API
- Add per-user lesson progress and say which endpoint writes it and how a repeated request stays idempotent
Deliverable: One page with the endpoint table, the error format and the progress endpoint.
Practice prompt ↗Practice prompt ↗Worked solution ↗05Proximity notifications from mobile locations
- Design the backend end to end: location ingestion, a queue, proximity matching and push delivery. Keep ingestion and processing clearly separate.
- Choose a spatial index (geohash, S2 or quadtree), design the schema and index around it, and explain how a proximity query uses it
- Cover delivery reliability: deduplicating notifications, retrying failed pushes, and keeping device state in sync after a missed message
- Explain how you would scale location writes when many devices report at once
Deliverable: A design sketch with the data flow, schema, index choice and one paragraph on notification reliability.
Practice prompt ↗Practice prompt ↗06Caching and booking data
- Work the availability worked exercise (drill-sql-1): size the derived and materialised options and write down the invalidation path
- Run the cache-stampede debugging drill (drill-debugging-5) and write its ordered checklist and fix
- Sketch an idempotent pipeline that syncs booking data from several supplier APIs: normalisation, deduplication keys, and how a retry avoids creating a duplicate record
- Outline a multi-layer caching plan for a hotel booking read path and say what each layer protects
Deliverable: Written answers for drill-sql-1 and drill-debugging-5, plus one supplier-sync sketch.
Practice prompt ↗Practice prompt ↗07Recruiter screen, panel stories and a full mock
- Write your salary range and a specific reason to build software for travel advisors, then say both aloud until they sound natural
- Prepare two project stories with fixed facts, including one where you explained a technical decision to a peer or manager
- Prepare the AI-tools answer: which tool, for which task, how you checked the output, and a case where you chose not to use it
- Run a mock with one array coding question and one design question, narrating your reasoning from the first sentence
Deliverable: A one-page answer sheet for the recruiter screen and panel, plus notes from one narrated mock.
Practice prompt ↗Practice prompt ↗Worked solution ↗Expand any day for tasks and deliverables. Your progress is saved on this device.
The behavioural questions reported for this role are about motivation, communication and working habits. Answer each one with a specific example that holds up under follow-up questions: the decision you made, who you explained it to, what changed as a result, or which tool you used and how you checked its output.
How do you handle authentication, identity propagation, and standardiz…
How do you handle authentication, identity propagation, and standardized error responses across nested API endpoints?
Approach
- Give the blast radius: what could have broken, and what you measured.
- Name the disagreement and how you resolved it with evidence.
- 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?
How do you approach incorporating AI productivity tools into your dail…
How do you approach incorporating AI productivity tools into your daily software engineering workflow?
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 did you decide not to do, and why?
- What would you do differently if you ran that again?
Reverse your own decision on hold expiry
You chose sweeper-only expiry for inventory_hold_night: a job flips lapsed rows from active to expired and returns units, and the availability read trusts the state column alone. Production contradicted the choice. Describe a decision you reversed: what you originally believed and why it was reasonable, the specific observation that changed your mind, how long you waited before reversing, what the reversal cost, and what you kept from the first design. Name the metric that moved and one that did not.
Approach
- Say what made the original choice reasonable, because a reversal story with no defensible starting point reads as carelessness rather than judgement. Sweeper-only expiry keeps the read path trivial and lets the partial index on (unit_type_id, stay_date) WHERE state = 'active' answer availability with no clock in the query.
- Give the observation that broke it, in numbers. When the sweeper falls behind by L seconds, up to L seconds of lapsed holds still read as active, so availability is understated and searches lose bookable inventory on exactly the dates where holds are densest, which are the dates that sell. Availability understatement is the metric that moves first.
- Separate it from the opposite failure so the reversal is not mistaken for a panic. A sweeper that releases a hold the booking path has already converted increments capacity back and oversells, which is why the release must be conditional on the active-to-released transition and not a bare increment. Both failures are real and they pull in different directions.
- State the reversal concretely: evaluate expiry against the clock at read and write time, filtering active rows by expires_at_utc, and keep the sweeper for cleanup. Name the constraint that shapes it, namely that now() is not immutable so 'not expired' cannot be a partial index predicate; expiry is always a filter over the active set, which is exactly why the sweeper has to keep that set small.
- Report the cost honestly: a re-tested read path, a migration of in-flight holds, and a measurable increase in rows filtered per availability query. Then name a metric that did not move, because a candidate who claims everything improved is not measuring.
- Close on how long you waited and what you would watch earlier next time. Waiting for two data points beats reversing on one anecdote; waiting for a quarter is a different mistake.
Follow-up
- What is the right TTL for a hold, and what evidence would set it rather than a round number?
- How do you stop the sweeper from releasing a hold that the booking path converted a millisecond earlier?
- If the sweeper falls two minutes behind under load, what does a traveller see and what does the revenue graph show?
- 01
Why are you interested in working at Fora Travel and building tools for the travel industry?
- 02
Tell me about a time when you had to clearly explain a technical decision to a peer or hiring manager.
- 03
How do you approach incorporating AI productivity tools into your daily software engineering workflow?
- 04
What are your salary expectations, and what draws you to travel technology?
- 05
What role do you see technology playing in modern travel advising?
Is this an official Fora Travel interview guide?
No. It is PracHub's own research and practice material for the Software Engineer role at Fora Travel. Rounds and questions reflect what candidates have reported, not a process Fora Travel has published, and they change over time. Confirm the current format and scope with your recruiter.
PracHub interview research ↗Which language should I use in the technical evaluations?
Candidates report that algorithmic tasks can be done in your preferred general-purpose language, while the frontend debugging work uses JavaScript, HTML and CSS. Practise array problems in the language you write most fluently, practise debugging in plain JavaScript and CSS, and confirm the environment with your recruiter.
PracHub interview research ↗How much does interest in travel matter?
Candidates report that the recruiter screen covers interest in travel technology alongside career history and salary expectations, and the reported questions include one about why you want to build tools for the travel industry. Prepare a specific answer tied to the work of travel advisors, such as managing bookings, client communication or legacy tools, rather than to your own trips.
PracHub interview research ↗Will I be asked about AI tools?
A reported behavioural question asks how you incorporate AI productivity tools into your daily engineering workflow. Answer with a concrete example: the tool, the task, how you checked its output, and a case where you decided not to use it.
PracHub interview research ↗What kind of coding questions come up?
The reported coding questions focus on array fundamentals: merging an unsorted array into the empty tail of another and sorting the result, stating the time and space complexity of that approach, and returning the first element that meets a filter or merge condition. Practise in-place array handling, edge cases such as empty inputs and duplicates, and stating Big-O for time and extra space without being asked.
PracHub Software Engineer practice ↗What should I study for the design questions?
The two reported design topics are REST APIs for a three-level Course, Module and Lesson hierarchy, and a mobile backend where clients periodically report their location to trigger proximity-based notifications. For the first, practise URI structure, payloads, pagination, authentication and a consistent error format. For the second, practise spatial indexing (geohash, S2 or quadtree), keeping ingestion separate from processing, and reliable push delivery.
PracHub Software Engineer practice ↗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