As a Software Engineer at Tripadvisor, you will build and scale the global technology infrastructure that connects hundreds of millions of travelers with places to stay, things to do, and places to eat. Operating across major brands including Tripadvisor, Viator, and TheFork, engineering teams at Tripadvisor solve complex marketplace problems spanning high-throughput search, distributed real-time booking, personalization, and multi-tenant content delivery. Your code directly impacts how millions of users discover, plan, and book travel experiences every single day.
The role demands a balance between technical depth and product-driven execution. Whether you are building low-latency RESTful APIs in Java, optimizing front-end performance in React, designing scalable microservices on AWS, or integrating machine learning models for travel recommendations, you will work in an environment that values clean architecture, continuous delivery, and operational excellence. Senior engineers in this role are expected to demonstrate strong technical leadership, owning end-to-end feature lifecycles from architectural design to post-deployment monitoring.
Joining Tripadvisor means stepping into a collaborative engineering culture where technical rigor is paired with autonomy. Candidates who thrive here possess strong computer science fundamentals, a pragmatic approach to problem-solving, and a focus on delivering measurable user impact at scale.
HR Recruiter Screen
reportedBefore anything technical happens, someone has to decide which rung of the ladder your loop is calibrated to, and that decision sets the bar for every round after it. It comes from how you describe scope, not from your title, because titles do not convert cleanly between companies. The weak version of the answer is team size and years. The strong version names the largest change you shipped where nobody reviewed the design, what would have broken if you had been wrong, and what you were paged for. Get the level said out loud on this call, because the range and the loop both follow from it.
What to demonstrate
- Whether the scope in your own account maps onto a level the team actually has an opening at, so a mismatch ends the process cheaply rather than after four interviewers have spent a day
- Whether your title needs re-mapping: the same word describes very different amounts of independent decision-making at a twenty-person company and a ten-thousand-person one
- Whether your compensation expectation can be filled at that level in the structure the role pays in, which is why the number gets asked for before any engineer is scheduled
How to prepare
- Write down two changes from the last two years: the largest one you designed with nobody reviewing the design, and the largest one where someone more senior did. Lead with the first when scope comes up, and be ready to say which parts of the second were yours
- Ask which level the loop is calibrated to and what changes at the level above it, then plan your weeks from that answer rather than from the posting
- Settle a total-compensation range beforehand with the split named, base against bonus against equity and its vesting period, so a question about numbers gets a number instead of the word market
Automated Assessment
reportedThe same problem is scored by two different mechanisms depending on the format, and preparing for one does not cover the other. With a person watching, partial progress is visible and a hint is a correction you can absorb; silence is the expensive failure, because nobody can read a half-written function. With an automated grader there is no partial credit for what you were about to do, nobody to ask, and the worked examples in the prompt are the entire specification. Read them as a contract, down to whether an empty result should be an empty list or no output at all.
What to demonstrate
- In a live session, whether your commentary tracks what your hands are doing, and whether a hint redirects you or gets defended against
- In an automated one, whether you cover the cases the examples do not show, since the hidden cases are where the score moves
- Whether you manage the clock on purpose: abandoning an approach that is not converging while there is still time to write something simpler that finishes
How to prepare
- Have someone hand you a problem and feed you one deliberately wrong hint. Practise testing it against a concrete case instead of accepting or rejecting it on authority.
- Do one timed run a week in a plain browser editor with autocomplete, linting and your own snippets switched off, which is closer to what these environments give you
- For the automated format, write the harness before the solution: a main that feeds the worked examples plus an empty and a single-element case and prints expected against actual, so a wrong submission is caught by you first
Technical Interviews
reportedMost 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.
Behavioral Interview
reportedThis round is deciding whether a change you make without supervision can be allowed to reach production. It is scored on what you knew at the moment you decided, not on how it turned out, so a story that opens with the result and works backwards reads as luck retold as judgement. Say what the options were, what you did not know, what you did to shrink the unknown before committing, and what you accepted as the worst plausible case. The detail that separates answers is a bound: how many users, how much data, and for how long, if you had been wrong.
What to demonstrate
- Whether the reasoning you give was available at the time you decided rather than after the result came in, since a story whose deciding evidence arrived later describes an outcome and not a judgement
- Whether you can put units on the exposure (users, rows, minutes of degraded service) and whether the containment you chose actually bounded it: a canary bounds the request path it fronts, while a background job writing to a shared table reaches every user regardless of which version served their requests
- Whether the reversal path existed before you shipped or was improvised during the incident, and whether it restores state or only stops further damage
How to prepare
- For your three largest changes, write down the one thing you would have had to be wrong about for it to fail, and what your best estimate of it was on the day you shipped. If you never held an estimate, that is the gap the follow-up questions will find
- Write the undo procedure for one of those changes as it existed at the time, then mark which steps restore data and which only stop new damage. Turning a flag off or reverting a deploy ends the new writes; rows already written come back only from a copy you kept, and a dropped column comes back empty unless something outside the schema holds the values
- Rehearse one story from the decision point forward and stop before the outcome, then have someone ask what you would do next. If the story only works with the ending attached, it is an anecdote rather than a decision you can defend
3 candidate reports. Individual accounts describe a particular role and hiring cycle.
Tripadvisor Software Engineer interview: fizz buzz technical prompt
I had an easy, straightforward process. The meeting invitation arrived well before the interview, which gave me time to prepare, and communication about the process was clear. The technical questions were light rather than deep system questions. I was asked fizz buzz, passed it, and moved through the process. I still did not get an offer. Location: London, England. Overall feedback: positive. Off…
Read full experienceTripadvisor Software Engineer interview: coding, design, and culture rounds
The Tripadvisor loop began with HR covering my background and fit, with a few quick basic technical checks. The next technical screen included an easy LeetCode-style question, OOP and data-structure discussion, and an open-ended technical problem. Later coding rounds had two LeetCode questions and discussion of time and space complexity. The last technical round combined another LeetCode problem…
Read full experienceTripadvisor Software Engineer Interview Experience — A Four-Level Frontend OA Building a Blog App
A couple of days after the referral I got an AI HR call — 15 minutes answering some behavioral questions — and then I received two separate OA links. OA1 was a 15-minute, 50-question IQ-style test plus a company culture questionnaire. I didn't do that well on it — I only got through 45 of the 50 questions, and I'd estimate my accuracy was somewhere around 35-40%. OA2 was a frontend CodeSignal OA:…
Read full experiencePracHub editorial advice for the preparation topics above.
Caching search results at the (property, date range, party) grain
That key is the cartesian product of destination, check-in, length of stay, party composition, currency, point of sale and promotion eligibility, so the hit rate is close to zero on anything but the most popular routes and the cache pays for itself only in memory. Worse, any input you forget to include in the key leaks one traveller's price to another, and price leaks of that kind are discovered by customers rather than by monitoring. Cache at the (unit_type, stay_date) grain instead: the key space is bounded by unit types times the forward horizon, every entry is reused across every length of stay and every flexibility variant that touches that night, and range composition plus restriction evaluation happens at request time on cheap in-memory rows. The tradeoff is that invalidation now has to be precise per unit-date, which is the right problem to have.
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.
Arguing past a hint
When the interviewer asks what happens for a particular input or floats a different data structure, stop and take it seriously; it is almost always a correction rather than idle curiosity. Talking over it converts a recoverable wrong turn into a data point about how you handle review.
Naming no test cases at all
State what you would test before being asked: empty input, a single element, all elements equal, the maximum permitted size, and the input that exercises the branch you just wrote. It costs thirty seconds and is much of what separates someone who has shipped code from someone who has only solved puzzles.
Choose a category, try a prompt, then open its approach, worked solution or follow-up when you need it.
Implement an LRU (Least Recently Used) Cache with constant time comple…
Implement an LRU (Least Recently Used) Cache with constant time complexity for get and put operations.
Approach
- Choose the data structure from the access pattern, not from familiarity.
- State the target complexity and say which constraint rules the naive version out.
- Restate the input: its shape, its size, and what is guaranteed about it.
Follow-up
- Which test case would catch an off-by-one here?
- What is the worst case, and how likely is it on real data?
Given a string containing brackets, determine if the input string is v…
Given a string containing brackets, determine if the input string is valid and properly balanced.
Approach
- Walk one small example through your approach before writing the whole thing.
- State the target complexity and say which constraint rules the naive version out.
- Restate the input: its shape, its size, and what is guaranteed about it.
Follow-up
- How does this change if the input no longer fits in memory?
- Which test case would catch an off-by-one here?
Write a function to check if a string is a palindrome or can be rearra…
Write a function to check if a string is a palindrome or can be rearranged to form a palindrome.
Approach
- Name the brute-force solution and its complexity before improving on it.
- Choose the data structure from the access pattern, not from familiarity.
- Restate the input: its shape, its size, and what is guaranteed about 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 array of stock prices over time, calculate the maximum profit…
Given an array of stock prices over time, calculate the maximum profit you can achieve by buying and selling a single stock.
Approach
- Walk one small example through your approach before writing the whole thing.
- State the target complexity and say which constraint rules the naive version out.
- Restate the input: its shape, its size, and what is guaranteed about it.
Follow-up
- How does this change if the input no longer fits in memory?
- Which test case would catch an off-by-one here?
Compare the structural and performance differences between HashMaps, H…
Compare the structural and performance differences between HashMaps, HashTables, and LinkedLists.
Approach
- Say what the runtime actually does before reasoning about the code.
- Identify the window where an invariant is briefly untrue.
- Reach for the cheapest primitive that closes the race, not the broadest lock.
Follow-up
- What happens if two callers reach this at the same time?
- Where could this allocate more than you expect?
Explain the key differences between abstract classes and interfaces in…
Explain the key differences between abstract classes and interfaces in Java, and when you would choose one over the other.
Approach
- Identify the window where an invariant is briefly untrue.
- Reach for the cheapest primitive that closes the race, not the broadest lock.
- Name what is shared across threads and what owns each piece of state.
Follow-up
- Where could this allocate more than you expect?
- How would you prove the race exists rather than suspect it?
Peak distinct unit types requested in a sliding minute
An append-only search log for one client gives events (ts_ms, unit_type_id) with non-decreasing ts_ms, up to 5,000,000 events. Automated scraping is characterised by breadth rather than volume: a person reloads one property, a crawler walks a catalogue. Return the maximum number of distinct unit_type_id values this client requested inside any 60,000 ms window under the half-open convention [t, t + 60000), and the window start at which that maximum is first reached. Target O(N) time, with space proportional to the distinct values alive in one window rather than to N.
Approach
- Two indices over the array. Advance
right, incrementcount[unit_type_id], and increment a separatedistinctcounter only on the 0 -> 1 transition. - Shrink from the left while
ts[right] - ts[left] >= 60000: decrement the count, decrementdistincton the 1 -> 0 transition, and erase the key. The half-open convention makes the condition>=rather than>; with>the window spans 60,000 ms inclusive and every reported peak drifts by the events sitting exactly on the boundary. - Track the running maximum of
distinctand thets[left]at which it was first reached, updating only on a strict increase so ties resolve to the earliest window. - Each index is admitted and evicted exactly once, so the total work is O(N) amortized even though the eviction is a while loop inside the scan. Erasing at count zero is what keeps space at window-distinct rather than session-distinct; leaving zero-count keys behind turns the map into an O(N) structure over a long session.
- Trade-off: this requires the client's events in timestamp order in one place. If they arrive out of order across ingest partitions, two pointers are invalid — you need a watermark plus a reorder buffer sized to the expected skew, which raises the memory bound from window-distinct to skew-distinct and adds the watermark delay to detection latency.
Worked solution 20 min
- Take seven events: (0, A), (10000, B), (20000, A), (30000, C), (59999, D), (60000, E), (61000, A).
- Walk
rightfrom 0 to 6, maintainingcount,distinctand the left pointer, and write the window contents at each step. - Note the one eviction: at right = 5,
60000 - 0 >= 60000, so left advances from 0 to 1 and A's count drops from 2 to 1 without changingdistinct. - Record the maximum distinct value and the
ts[left]at the step where it is first reached. - Separately compute the maximum event count over the same windows and compare the two answers.
Follow-up
- Make it streaming with bounded memory and approximate distinctness. What does HyperLogLog cost you in accuracy at the thresholds you would actually alert on, and can you still evict from the left?
- The same client_id is shared by a corporate NAT. What second signal would you combine with breadth before acting, and why is breadth alone not enough?
- How does the answer change if you need the peak over all clients simultaneously rather than one at a time?
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?
Reconcile night revenue against captures without fanning either out
booking(booking_id, unit_type_id, state, total_minor, currency_code) has booking_night(booking_id, stay_date, unit_type_id, night_price_minor, tax_minor, fee_minor, state) one-to-many, and payment_transaction(payment_transaction_id, booking_id, kind, state, amount_minor) one-to-many. A report joins all three and sums, and on a three-night booking with a deposit plus a balance capture both totals are wrong. Name both multiplications precisely, then write the query giving nights sold, night gross and net captured per property per stay month, and state how a booking-grain capture is attributed to the months its nights fall in.
Approach
- Name the cardinality: booking to booking_night is one-to-N and booking to payment_transaction is one-to-M, so joining both produces N times M rows per booking. Every night is counted M times and every payment N times, and the two columns are inflated by different factors, which is why the report looks plausible rather than obviously broken. The defect is aggregating a measure across a row set two children have expanded, not the SUM itself.
- Reject the reflexive fixes. DISTINCT and SUM(DISTINCT ...) de-duplicate values rather than rows, so two genuine nights priced identically collapse into one and the total becomes wrong in the other direction while looking tidier.
- Aggregate each child in its own CTE before joining anything, and filter on kind and state rather than on sign: the capture total is SUM(amount_minor) FILTER (WHERE kind = 'capture' AND state = 'succeeded') minus the same expression for refunds, grouped by booking_id. Authorisations are not revenue and 'unknown' is not a failure, so neither may be counted. SUM with FILTER returns NULL rather than zero when nothing matches, so wrap both sides in COALESCE before subtracting or a booking with no refund reports nothing at all.
- Face the grain mismatch instead of joining through it. Nights are the consumption grain and a capture is at booking grain, so no join makes them directly comparable. Either report the two at their own grains and reconcile explicitly, or allocate each booking's net capture across its nights in proportion to night gross, in integer minor units, distributing the remainder largest-remainder first with a stable tiebreak on stay_date.
- Take the month from booking_night.stay_date and never from booking.created_at, because one booking's nights routinely fall in two months and two tax periods. date_trunc over a date argument returns a timestamp, so cast back to date if the grouping key is meant to be one.
- Filter on booking_night.state rather than booking.state, since a booking can be partially cancelled and the night rows are where that is recorded. Assert that the sum of night price, tax and fee equals booking.total_minor per booking as a test, because per-night, per-jurisdiction rounding is exactly where that identity breaks.
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 would you design a distributed web crawler and tagging system to p…
How would you design a distributed web crawler and tagging system to process travel reviews across millions of POIs (Points of Interest)?
Approach
- Name the failure you are designing for, then the recovery path.
- Choose a partition key and say what query it makes expensive.
- State the consistency you need, and where you are willing to be stale.
Follow-up
- How does this behave when that dependency is down for an hour?
- What breaks first when traffic grows ten times?
Design a scalable rate limiter to protect backend APIs from excessive …
Design a scalable rate limiter to protect backend APIs from excessive traffic spikes and denial-of-service attempts.
Approach
- Choose a partition key and say what query it makes expensive.
- State the consistency you need, and where you are willing to be stale.
- Name the failure you are designing for, then the recovery path.
Follow-up
- What would you drop to keep the system up under load?
- How does this behave when that dependency is down for an hour?
Debug a snippet of asynchronous JavaScript code containing race condit…
Debug a snippet of asynchronous JavaScript code containing race conditions and unexpected state mutations.
Approach
- Say what you would check first and why it is the highest-information step.
- Clarify what is being asked and what a complete answer contains.
- Work from the requirement backwards to the design.
Follow-up
- How would you know your answer was wrong?
- What assumption would you test first?
Cache the rate calendar at the unit-night grain
Availability reads run at 40,000/s. ari_daily holds one row per (unit_type_id, stay_date) across a 450-day horizon for 500,000 unit types, and ari-ingest rewrites parts of it continuously. A colleague proposes caching on (property, check-in, nights, party, currency). Design the cache at the (unit_type_id, stay_date) grain instead: state the memory footprint, how a length-of-stay range is composed and restriction-checked from cached rows, exactly what one supplier push invalidates, and how you stop a stampede on a popular date.
Approach
- Size both key spaces before choosing. The request grain is the product of destination, check-in date (450), length of stay (1-28), party composition, currency and point of sale, so entries are barely reused outside a handful of routes and the cache pays for itself only in memory. Worse, any input omitted from that key serves one traveller another traveller's price, and that class of bug is found by customers rather than by monitoring.
- Size the night grain: 500,000 unit types times 450 dates is 2.25 x 10^8 entries. Packed payload is about 24 bytes (price as int64 minor units, remaining_units int16, a restriction bitfield, min/max LOS, the supplier sequence), so payload alone is roughly 5 GB and realistic per-entry key and store overhead puts the working set in the low tens of gigabytes: a sharded memory tier, not one node.
- Compose ranges at request time. For a stay [check_in, check_out) of n nights, read n+1 rows, check_in through check_out inclusive, because closed_to_departure is evaluated on the checkout date and the checkout date is not itself a booked night. The stay is bookable when every night in [check_in, check_out) has remaining_units >= units and stop_sell false, the arrival night is not closed_to_arrival and satisfies min_los and max_los, and the checkout date is not closed_to_departure. That is O(n) in-memory comparisons with n at most about 30, so a plus-or-minus-three-day search over three unit types is on the order of 10^2 compositions over a few hundred cached rows.
- Invalidate per unit-date by writing through from ari-ingest: one push touching (u, d) overwrites exactly one key. Under the request-grain cache that same push invalidates every cached range covering d, which is every arrival date within max_los of it crossed with every length, on the order of 10^2 to 10^3 keys you would have to enumerate or else knowingly serve stale.
- Handle the hot date: single-flight per key so a miss produces one loader and every other caller waits on the same result, cache negative lookups so a genuinely absent row does not hit the database on every request, and keep a short jittered TTL only as a backstop behind write-through rather than as the freshness mechanism.
- State the boundary: the cache holds inventory facts per night and never a composed price. Taxes, fees, promotions and FX are quote-time composition in pricing-and-offer, which is precisely what keeps party composition and currency out of the cache key.
Worked solution 20 min
- Load ari_daily with 50,000 unit types across 450 dates (22.5M rows) and populate a cache process with the packed per-night representation; divide resident memory by entry count to get real bytes per entry.
- Implement compose(check_in, nights, units) over n+1 cached rows, applying each restriction at its correct night, with fixtures for a range whose middle night is closed_to_arrival and a range whose checkout date is closed_to_departure.
- Replay an hour of search traffic against both cache designs and record hit rate and resident bytes for each.
- Push one ARI update for a single (unit_type_id, stay_date) and count the keys each design must invalidate.
Follow-up
- Ingest applies a full refresh for one unit type's entire horizon. What does the cache do, and how do you stop 450 invalidations from becoming 450 round trips?
- The node holding a hot city's unit types dies at peak. What does search return over the next 30 seconds, and what does it tell the caller?
- How does a reader distinguish a cached row that is stale from one that is merely old?
Search latency doubles after a tax lookup lands
Search p99 went from 620ms to 1.4s after a release. No statement in the slow-query log exceeds 4ms, database CPU is up 40%, and the connection pool now reports acquire wait time. The release moved per-jurisdiction tax rates out of the in-memory ruleset and into a lookup performed during pricing. A 30-result page prices three unit types per property over a three-night stay. Deliverable: an ordered diagnostic checklist, the arithmetic for the per-request statement count, and the fix.
Approach
- Establish that the regression is in statement count, not statement duration: diff pg_stat_statements calls per request between the two releases, or count spans per trace. A flat mean duration beside a large calls delta is the N+1 signature, and the slow-query log cannot show it by construction because no individual statement is slow.
- Multiply the fan-out out loud: 30 results x 3 unit types x 3 nights = 270 lookups per page, at least one per night per jurisdiction. At 200 requests/second that is roughly 54,000 statements/second arriving at a pool of a few dozen connections.
- Separate service time from queueing: compare pool acquire time against mean statement time. Latency rising while statement duration is unchanged means the pool is the constraint, and wait time grows sharply as utilisation approaches saturation rather than proportionally to load.
- Fix at the right level: restore the pure, versioned in-memory ruleset, since the offer service is specified to do no per-call I/O and a pinned ruleset version is what makes a quote replayable in a dispute months later. If a read is genuinely unavoidable, issue one batched query per request keyed by (jurisdiction, ruleset_version) -- O(1) round trips instead of O(results x nights).
- Add the guard that catches the next one: assert a per-request statement-count ceiling in an integration test, so a reintroduced lookup fails CI rather than p99.
Follow-up
- With the ruleset in memory, what happens when tax rules change mid-flight, and what does the quote pin so a capture or refund months later reconciles against the amount the traveller agreed to?
- Pool acquire time is now the bottleneck metric. What do you measure before raising pool size, and what breaks when the fleet's total pool exceeds the database's connection budget?
For a candidate senior enough that the loop turns on design and judgement rather than on whether the coding round gets finished. Five days build one system properly and then stress it; coding gets a single maintenance day, on the assumption that the risk at this level is an unexamined tradeoff rather than a missed algorithm.
Prepare, practise & reflect
One practical outcome each day. Spend longer where you need it.
0 / 7 done01Numbers before diagrams
- Build your own reference card of the figures you will re-derive all week: bytes for a realistic record, requests per second implied by a given daily active count, and the storage that a year at a given write rate produces. Derive each one rather than copying it, because the derivation is what survives a follow-up.
- Turn one product statement into capacity requirements. From ten million daily users at four writes and forty reads each, state the peak-to-average factor you are assuming and why, then produce peak write QPS, peak read QPS and a year of storage.
- Write the two numbers whose order of magnitude changes the design, the read-to-write ratio and the working-set size against memory per node, and state the threshold at which each one flips your answer.
Deliverable: A one-page numbers card and one worked capacity estimate with every assumption written down.
Practice prompt ↗Practice prompt ↗Practice prompt ↗Worked solution ↗02One system, from requirements to schema
- Spend the first ten minutes producing only functional requirements, non-functional targets with numbers attached, a p99 latency, a durability expectation, a consistency requirement, and an explicit out-of-scope list.
- Define the interface before the boxes: the three or four endpoints, their parameters, what each returns, and which of them are idempotent.
- Write the data model, then write the single access pattern that justifies it, and state what the schema would have to become if the dominant access pattern were the other one.
Deliverable: One design carried to endpoint-and-schema depth, with non-functional targets expressed as numbers and a written out-of-scope list.
Practice prompt ↗Practice prompt ↗Practice prompt ↗03The consistency you are actually buying
- Write out what a client sees under asynchronous replication when its write commits on the leader and its next read is served by a lagging follower, then write the two fixes, pinning that session's reads to the leader for a bounded window or carrying a version token the replica must reach, and the cost of each.
- Work the quorum arithmetic on paper for N of three with W and R of two, and separate what R + W > N does guarantee, that any read set intersects any write set, from what it does not: on its own it is not linearizability, and a sloppy quorum that accepts writes on nodes outside the preference list breaks even the intersection.
- Take two storage choices with different defaults, a single-leader relational store committing synchronously and a quorum-replicated store that converges eventually, and write the specific product behaviour that would be wrong under each, rather than a general statement about which is stronger.
Deliverable: A page separating what quorum overlap guarantees from what it does not, with one concrete product misbehaviour attached to each gap.
Practice prompt ↗Practice prompt ↗Practice prompt ↗04Failure is the design
- For one write path, work through the case where the client times out after the server has already committed, then design the idempotency key: who generates it, how long it is retained, and what the duplicate request returns.
- Express the retry policy as parameters rather than as a word: maximum attempts, base delay, backoff factor, jitter, and which error classes are retried at all. Then state why retrying a non-idempotent write without a key is a correctness bug and not merely waste.
- Compute the fan-out effect on tail latency. If a request waits on ten backends and each independently exceeds its p99 one percent of the time, the chance at least one is slow is 1 - 0.99^10, about ten percent. Then write why independence is the optimistic assumption and what correlates them in practice.
- Name the backpressure mechanism for one queue or one dependency in the design, a bounded queue with shedding or a concurrency limit, and write what the caller is told when it engages.
Deliverable: One write path with an idempotency design, a parameterised retry policy, and a written tail-latency calculation with its assumption named.
Practice prompt ↗Practice prompt ↗Worked solution ↗05Scaling the hot path
- Choose cache-aside or write-through for one read path and write the staleness window each produces, then name the invalidation event and what the system does when that event is lost.
- Design against the stampede: either coalesce requests so only one recomputes a missing key, or refresh early with jittered expiry, and write why identical TTLs on keys populated in the same moment produce a synchronised expiry and a thundering herd.
- Shard one table by a key you choose, then answer the two questions that break the choice: which queries now require a scatter-gather, and what happens to the distribution when one tenant is a hundred times larger than the median.
- Write the cost of adding a node under plain modulo placement, where nearly every key moves, against consistent hashing, where roughly one key in n+1 moves, and state what virtual nodes are for.
Deliverable: A caching and sharding decision for one path, each with its failure mode and its rebalancing cost written beside it.
Practice prompt ↗Practice prompt ↗06Keep the coding hand in, at the bar that applies to you
- Solve one medium problem in thirty minutes, then spend twenty more making it production-shaped: named invariants, validation at the boundary, and errors that distinguish a caller mistake from an internal fault.
- Write the tests you would require of a colleague's version of that function: one for empty input, one for the boundary, and one for the case the implementation is most likely to get wrong.
- Read a piece of your own code from six months ago and write the change you would ask for, phrased as you would actually phrase it in review.
Deliverable: One problem hardened to review standard, with its test list and one written review comment.
Practice prompt ↗Practice prompt ↗07Defend it while being interrupted
- Run a forty-five-minute design mock with an interviewer briefed to change a requirement halfway, a tenfold traffic increase or a new strict consistency requirement, and to push on one number you estimated.
- Rehearse the two sentences a senior loop is listening for: naming the tradeoff you are choosing against and why, and saying what you would measure to learn that the choice was wrong.
- Prepare the design you regret: a real decision, the constraint that produced it, what it cost, and what you changed afterwards.
Deliverable: Mock notes recording how the design changed under the new requirement, plus a written account of one regretted decision.
Practice prompt ↗Practice prompt ↗Worked solution ↗Expand any day for tasks and deliverables. Your progress is saved on this device.
Counting review comments or mentees proves nothing. The useful version is a specific change you approved with a reservation you stated, or one you blocked and the delay that cost. Say which standard you were holding and why it was worth the friction. A mentoring story needs the thing the other person can now do without you.
Give an example of how you mentored a junior engineer or drove enginee…
Give an example of how you mentored a junior engineer or drove engineering standards within your previous team.
Approach
- Give the blast radius: what could have broken, and what you measured.
- Pick a story where you made the decision, not one where you watched it.
- Name the disagreement and how you resolved it with evidence.
Follow-up
- How did you know your change caused the improvement?
- What would you do differently if you ran that again?
Estimate an ingest rewrite you have never attempted
You are asked to estimate replacing the ARI ingest path so that a supplier's full refresh applies as a set difference over the affected horizon instead of row-by-row upserts, across roughly 450 forward days for hundreds of thousands of unit types, with no degradation of the search read path during the change. You have never built this. Give the estimate, the decomposition behind it, the three unknowns that dominate the range, the spike you would run first, and the condition under which you would come back and re-estimate.
Approach
- Decompose into shippable pieces before producing any number: refresh semantics, the write path itself, a dual-run comparison against the current path, per-supplier cutover, and rollback. Estimating the whole as one figure is the failure mode; estimating five pieces gives you somewhere to put the uncertainty.
- Name the semantic unknown first, because it dominates and it is not an engineering problem. A set difference needs the affected horizon, and suppliers differ in whether a full refresh declares its horizon or leaves you to infer it. If you must infer, the correct behaviour on an ambiguous message is a decision with a revenue consequence: withdraw too much and you stop selling live dates, withdraw too little and you keep selling dates the supplier has pulled.
- Quantify the write-amplification unknown rather than describing it. One refresh rewriting a 450-day horizon for a large supplier is millions of unit-date writes in minutes against the hottest table in the system; in PostgreSQL every update writes a new row version, so this produces dead tuples and vacuum pressure, and HOT updates only avoid index churn when no indexed column changes and the page has room. Whether the read path survives that is measurable, not arguable.
- State the third unknown as the one that is usually discovered late: how many suppliers actually send full refreshes, how often, and how large, because the cutover cost is per supplier and the tail of odd behaviour is where the schedule goes.
- Design the spike to collapse the widest unknown in the least time: replay the largest historical refresh you have against a copy of the table and measure rows touched, wall clock, dead tuples produced and the search path's p99 during the replay. Two days of that is worth more than a week of design.
- Give the estimate as a range with the multiplier attached to a named unknown, plus a re-estimate trigger such as the replay exceeding a stated p99 impact. A single number with no stated dominating unknown is the answer that gets you held to it.
Follow-up
- Halfway in, the replay shows the read path degrading badly. What do you change, and does the estimate move?
- How would you run both paths in parallel and prove they agree, without doubling the write load on the hot table?
- Someone needs a date to give a partner. What do you commit to, and what do you explicitly not commit to?
Resolve a review disagreement over an inventory decrement
A colleague's pull request reads remaining_units from ari_daily, checks it against the requested units in application code, then issues UPDATE ari_daily SET remaining_units = remaining_units - :n for each night. You believe it must be one conditional UPDATE per night with an explicit row-count check, and that the isolation level has to be a stated decision. Describe a code review disagreement of this kind that you had: what you wrote in the comment, what evidence ended it, what you conceded, and what the engine actually does under the isolation level the code assumed.
Approach
- Write the failing interleaving in the comment rather than a principle. Two requests read remaining_units = 1, both pass the application check, both decrement, and the row lands at -1 or is clamped by the CHECK into a constraint error that surfaces as a 500 rather than as sold out. A four-line trace is harder to wave away than 'this is a race'.
- Give the replacement as a statement, not a description: UPDATE ari_daily SET remaining_units = remaining_units - :n WHERE unit_type_id = :u AND stay_date = :d AND remaining_units >= :n, one per night, inside one transaction, with every statement's row count inspected before commit. Say that the CHECK (remaining_units >= 0) is the last line of defence and not the concurrency control.
- State the isolation behaviour precisely for the engine in play, because this is where reviews go in circles. Under PostgreSQL READ COMMITTED a blocked UPDATE re-reads the row version that the blocker committed and re-evaluates its WHERE clause, so the guard still holds and a losing writer gets zero rows. Under REPEATABLE READ or SERIALIZABLE the same statement instead raises a serialisation failure, SQLSTATE 40001, which the caller must catch and retry with a bounded budget. Those are different code paths.
- Raise the second defect while you are there: the nights must be locked in ascending stay_date order. Two multi-night bookings over overlapping ranges that lock in request order deadlock, and that is a separate bug from the lost update.
- End the disagreement with a runnable artefact. A two-connection test that interleaves the two transactions and asserts the second gets zero rows takes twenty minutes and converts the discussion into a red test, which is the only thing that reliably ends this class of argument.
- Concede what is genuinely arguable. Retry budget, whether to hold or to take inventory at a different step, and whether the allowance applies here are judgement calls; the read-then-write is not.
Follow-up
- The author says the window is microseconds and the traffic is low. What is your answer?
- Where exactly do you put the retry for a 40001, and what is the budget before you give up on the booking?
- How would you write the test so it fails reliably in CI rather than one run in fifty?
- 01
Give an example of how you mentored a junior engineer or drove engineering standards within your previous team.
- 02
You are asked to estimate replacing the ARI ingest path so that a supplier's full refresh applies as a set difference over the affected horizon instead of row-by-row upserts, across roughly 450 forward days for hundreds of thousands of unit types, with no degradation of the search read path during the change. You have never built this. Give the estimate, the decomposition behind it, the three unknowns that dominate the range, the spike you would run first, and the condition under which you would come back and re-estimate.
- 03
A colleague's pull request reads remaining_units from ari_daily, checks it against the requested units in application code, then issues UPDATE ari_daily SET remaining_units = remaining_units - :n for each night. You believe it must be one conditional UPDATE per night with an explicit row-count check, and that the isolation level has to be a stated decision. Describe a code review disagreement of this kind that you had: what you wrote in the comment, what evidence ended it, what you conceded, and what the engine actually does under the isolation level the code assumed.
Is this an official Tripadvisor interview guide?
No. It is PracHub's own research and practice material for the Software Engineer role at Tripadvisor. Rounds and questions reflect what candidates have reported, not a process Tripadvisor has published, and they change over time. Confirm the current format and scope with your recruiter.
PracHub interview research ↗What is the primary coding language used during Tripadvisor interviews?
You are generally permitted to use any mainstream programming language you are most comfortable with, such as Java, Python, C++, or JavaScript. However, because much of Tripadvisor's backend stack is built on Java and Python, demonstrating fluency in these languages is advantageous.
PracHub interview research ↗How difficult are the live coding questions compared to standard industry platforms?
The coding challenges range from easy-medium to medium difficulty. Interviewers prioritize practical code structure, readability, edge-case handling, and clear communication over overly complex or obscure mathematical brainteasers.
PracHub interview research ↗Does Tripadvisor conduct take-home assignments or live coding screens?
The process varies depending on the region and level. Some candidate pipelines include a timed take-home coding exercise or an online assessment screen, while others jump directly into live shared-editor pair-programming sessions with engineering staff.
PracHub interview research ↗How heavily is System Design tested for mid-level vs. senior roles?
For entry-to-mid-level software engineers, system design questions are lighter and focus primarily on object-oriented design and basic API patterns. For senior software engineering roles, system design is a critical knockout round focusing on scalability, distributed caching, database partitioning, and microservices architecture.
PracHub interview research ↗Sources & methodology 3 sources ↗
Official role evidence, timestamped platform data and clearly labeled preparation advice.
- 01PracHub interview research ↗
PracHub editorial research into this company and role, maintained with this guide. Candidate-reported, not an employer publication.
platform · Accessed 2026-09-24 - 02PracHub Software Engineer practice ↗
Cross-company practice questions for this role.
platform · Accessed 2026-09-24 - 03PracHub interview preparation framework ↗
The framework the preparation plan follows.
platform · Accessed 2026-09-24