Etsy · Software Engineer
Updated · 2026-09-24

Etsy Software Engineer
Interview Guide

THE 60-SECOND BRIEF

As a Software Engineer at Etsy, you will build and scale systems that power a global e-commerce marketplace connecting tens of millions of buyers with millions of creative sellers. Engineering at Etsy directly drives the core user experience, from high-concurrency search engines, seller inventory management, and real-time messaging, to machine learning infrastructure, personalized recommendations, and checkout pipelines.

Seniority moves the scope further than the words in the title do. An earlier-career loop mostly checks that you implement something correctly and can reason about its cost, while a senior loop checks that you can pick between two defensible designs and say what you gave up.

Etsy candidates report 3 rounds · ≈ 3-5 weeks. The stages below are what candidates describe, not a published process.

Index provider locations for bounded proximity queriesReconcile money with append-only double-entry postingsShard hot markets while preserving per-booking ordering

39 min read

Practice 14 Software Engineer prompts
2Candidate experiences ↗Read their reports
14Practice promptsAcross five skill areas
3With worked solutionsIncluded in the practice prompts

As a Software Engineer at Etsy, you will build and scale systems that power a global e-commerce marketplace connecting tens of millions of buyers with millions of creative sellers. Engineering at Etsy directly drives the core user experience, from high-concurrency search engines, seller inventory management, and real-time messaging, to machine learning infrastructure, personalized recommendations, and checkout pipelines.

The engineering organization operates at significant technical scale, handling millions of requests per minute across a distributed microservices and web architecture built on JavaScript/Node.js, Python, PHP, and modern frontend tools like React. Engineers are expected to balance raw system scalability with user-facing product value, taking end-to-end ownership of services from design and implementation through deployment, monitoring, and iterative improvement.

What makes the Software Engineer position distinct at Etsy is its heavy emphasis on practical, real-world software engineering over abstract computer science puzzles. You will work on production systems where code quality, observability, database optimization, and cross-functional alignment with product managers and designers are evaluated just as rigorously as system capacity and algorithmic efficiency.

01

Phone Screen

reported

Before anything technical happens, someone has to decide which rung of the ladder your loop is calibrated to, and that decision sets the bar for every round after it. It comes from how you describe scope, not from your title, because titles do not convert cleanly between companies. The weak version of the answer is team size and years. The strong version names the largest change you shipped where nobody reviewed the design, what would have broken if you had been wrong, and what you were paged for. Get the level said out loud on this call, because the range and the loop both follow from it.

What to demonstrate

  • Whether the scope in your own account maps onto a level the team actually has an opening at, so a mismatch ends the process cheaply rather than after four interviewers have spent a day
  • Whether your title needs re-mapping: the same word describes very different amounts of independent decision-making at a twenty-person company and a ten-thousand-person one
  • Whether your compensation expectation can be filled at that level in the structure the role pays in, which is why the number gets asked for before any engineer is scheduled

How to prepare

  • Write down two changes from the last two years: the largest one you designed with nobody reviewing the design, and the largest one where someone more senior did. Lead with the first when scope comes up, and be ready to say which parts of the second were yours
  • Ask which level the loop is calibrated to and what changes at the level above it, then plan your weeks from that answer rather than from the posting
  • Settle a total-compensation range beforehand with the split named, base against bonus against equity and its vesting period, so a question about numbers gets a number instead of the word market
PracHub interview research
02

Technical Interview

reported

Input bounds are the part of the prompt most often skimmed, and they usually contain the answer. They tell you which complexity class is admissible, which narrows the search before you have thought about the problem itself. As a rough planning figure, a compiled language does on the order of 10^8 simple operations per second and an interpreted one roughly an order of magnitude less. So n up to about twenty admits enumerating subsets, a few thousand admits a quadratic pass, and a million admits neither: you need near-linear, or linear with a log factor. If the bounds are missing, ask for them.

What to demonstrate

  • Whether the approach is justified by the stated input size rather than by whichever pattern you recognised first
  • Whether you ask about the properties that change the algorithm: whether the input arrives sorted, whether duplicates occur, whether values are bounded integers, whether it all fits in memory
  • Whether you can name the bottleneck in your own solution and what would remove it, even when you deliberately leave it in place
  • Whether a claimed speedup is real, since memoising a recursion only helps when subproblems genuinely overlap and the state can be keyed cheaply

How to prepare

  • For each algorithm you rely on, write down the largest n it handles in roughly a second, then check two of those figures by timing them in the language you will actually type in
  • For two weeks, write one line naming your target complexity and the bound that justifies it before you write any code, then compare that line with what you ended up submitting
  • Practise the conversion backwards: given a required O(n log n), list the mechanisms that get you there (sorting, a heap, an ordered map, divide and conquer) and choose by what the problem needs to query, not by what you used last
PracHub interview research
03

Onsite Interviews

reported

A day like this is several different games in a row, and the expensive mistake is carrying the previous one into the next room. Coding rewards narrow precision and finishing inside a timer. Design rewards breadth, stated assumptions and naming what you are deliberately not building. Behavioural rewards specificity about people and decisions. Candidates who over-engineer a coding problem they were supposed to finish, or who start sketching class hierarchies before anyone has agreed what the system has to do, are usually still playing the last round. Between rooms, name out loud which game the next one is.

What to demonstrate

  • Whether the coding round ends with something that runs and has been traced against a degenerate input, rather than an extensible design that was never finished
  • Whether a design discussion opens by agreeing on traffic shape, read-to-write ratio and what is allowed to be stale, instead of proceeding from an architecture you arrived with
  • Whether a behavioural answer names a person, a disagreement and what you did about it, rather than describing the system the story happened inside
  • Whether the opening habits still appear late in the day: restating the problem, asking for constraints, saying the plan before typing

How to prepare

  • Book three mocks of different types back to back on one afternoon and ask each interviewer afterwards which round you answered in the wrong mode
  • Write a three-line opening script per round type — coding: restate, name the approach and its cost, then type; design: ask for scale, read-write mix and what must not break; behavioural: name the person, the stakes and the decision — and run it off a card so the switch is mechanical rather than remembered
  • Practise coding with a timer you do not extend, stopping when it stops, so the trained reflex is to finish a correct solution rather than to keep improving one
PracHub interview research

2 candidate reports. Individual accounts describe a particular role and hiring cycle.

PracHub editorial advice for the preparation topics above.

01

Treating the provider's device as the source of truth for state

The app is offline part of the time and buffers transitions, so events arrive late, out of order, and occasionally duplicated after a retry. A status column written last-write-wins will therefore accept 'started' after 'completed' and resurrect a cancelled booking, which re-enables charging on it. Client timestamps make it worse because the device clock is user-settable and drifts, so ordering by them is not ordering at all. The server must assign the ordering (a per-aggregate monotonic sequence) and every transition must be a guarded write against the expected current state, with the client event treated as a request to transition rather than as the transition.

02

Filtering providers with a bounding box or a distance function over raw lat/lon columns

A WHERE clause computing great-circle distance per row cannot use a B-tree index and degenerates to a scan of every provider in the table, which is fine at 5k providers in staging and falls over at 100k in a dense market at peak. A lat/lon bounding box is index-assisted but returns a square that over-selects badly near the poles and still needs a second-pass distance filter. The working answers are a spatial index — PostGIS GiST with ST_DWithin (which is index-assisted, unlike ST_Distance in a predicate) or the KNN <-> operator for ordered nearest-neighbour — or cell-based bucketing in a key-value store. Cell bucketing has its own edge: two points metres apart can sit in different cells, so a prefix-only lookup silently misses the closest provider unless the query also covers the eight neighbouring cells.

03

Answering a debugging question with a guess instead of a bisection

Give a procedure that halves the search space at each step: confirm the symptom reproduces, establish the last known-good version, input or timestamp, then bisect over commits, over the data, or over the layers of the request path. A plausible cause with no way to confirm it is the same move whether it happens to be right or wrong, which is why it scores nothing.

04

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.

Choose a category, try a prompt, then open its approach, worked solution or follow-up when you need it.

11 technical prompts3 include a worked solution

Enumerate the eight neighbours of a geohash cell

medium
bit manipulationparsinggeospatialencoding

Provider presence is bucketed by a 7-character geohash over the base-32 alphabet 0123456789bcdefghjkmnpqrstuvwxyz, five bits per character, with longitude taking the first bit and every other bit thereafter. Write a function that takes a geohash-7 string and returns its neighbouring geohash-7 cells. Handle the antimeridian and the poles explicitly rather than by accident. State the time and space complexity, what a geohash-7 cell measures on the ground, and how that measurement changes with latitude.

Approach
  1. Decode the string: map each character to its 5-bit value through a lookup table or an index into the alphabet, remembering that a, i, l and o are absent, so a naive ASCII offset is wrong. Seven characters give 35 bits.
  2. De-interleave those 35 bits. Bit 0 is longitude and the parity alternates, so longitude takes the 18 even positions and latitude the 17 odd ones, giving a grid of 2^18 columns by 2^17 rows.
  3. The neighbours are (lat_index + dy, lon_index + dx) for the eight non-zero (dx, dy) pairs. Longitude wraps: take lon_index modulo 2^18, so a cell at +180 degrees is genuinely adjacent to one at -180. Latitude clamps: a cell in the top or bottom row has no neighbour beyond it and returns fewer than eight. Wrapping latitude the same way teleports across the pole to an unrelated longitude.
  4. Re-interleave and re-encode in 5-bit groups. The whole operation is O(L) time and O(1) space for L = 7 characters, and is pure integer arithmetic — no trigonometry is involved in finding neighbours.
  5. Ground size: a geohash-7 cell is roughly 153 m east-west at the equator (360 degrees / 2^18 = 0.001373 degrees, times about 111,320 m per degree) and roughly 152 m north-south (180 degrees / 2^17, times about 110,574 m per degree). The north-south extent is essentially constant; the east-west extent scales with cos(latitude), so at 60 degrees the cell is about 76 m wide. A ring count must therefore be derived from the radius in metres at the query's latitude, not fixed once from an equatorial figure.
  6. State why neighbours are needed at all: two providers ten metres apart can sit in different cells when they straddle a boundary, so a prefix-only lookup silently misses the nearest provider. Querying the 3x3 block guarantees that any provider within the cell's smaller dimension of the query point is in the candidate set.
Follow-up
  • How many rings do you need for a 2 km radius at latitude 55, and how do you compute that rather than guess?
  • What does the string-prefix property buy you here that an S2 or H3 cell id does not, and what does it cost?
  • The 3x3 block returns 3,000 providers at peak — what do you do before computing any distances?

Detect unbalanced ledger transactions in a single pass

easyWorked solution
hashingaggregationledgerinvariants

You are given up to 50 million ledger_entry rows streamed in arbitrary order: entry_id, transaction_id, account_id, direction ('debit' or 'credit'), amount_cents (a positive bigint), currency, and idempotency_key. The invariant is that the signed postings of every transaction_id sum to zero. Report every transaction that violates it, together with its residual in cents. Duplicate rows sharing an idempotency_key may appear because a consumer retried a batch. Target one pass, with memory proportional to the transactions currently open rather than to the input length.

Approach
  1. Key the accumulator on (transaction_id, currency), not transaction_id alone. Balance is a per-currency property; bucketing across currencies lets a 4000-cent USD leg and a 4000-cent EUR leg cancel into a false pass. A genuinely multi-currency event balances through an FX clearing account with two balanced halves.
  2. Fix a sign convention once — debit +1, credit -1 — and accumulate in signed 64-bit integer cents. Never use a float: a double represents only integers below 2^53 exactly, and money comparisons against zero must be exact, not within an epsilon.
  3. Handle duplicates by scope. If the source is a table scan, the UNIQUE constraint on idempotency_key means duplicates cannot exist and no dedupe is needed. If the source is a replayed event stream, keep the seen idempotency_keys only for transactions still open and free them when the transaction is emitted, so dedupe memory stays O(open) rather than O(n).
  4. At end of stream emit every bucket whose residual is non-zero. Time O(n), space O(d) where d is the number of distinct open (transaction_id, currency) pairs — which for a well-behaved feed is small because postings of one event are written together.
  5. If the source can be ordered or hash-partitioned by transaction_id, memory drops to O(1) per group and the job parallelises cleanly: hashing on transaction_id guarantees every posting of one event lands in the same worker, so no worker ever sees a partial transaction.
Worked solution 20 min
  1. Take four transactions. T1 capture: debit consumer_receivable 4000, credit provider_payable 3200, credit platform_revenue 800. T2 partial refund: credit consumer_receivable 1000, debit provider_payable 800, debit platform_revenue 200. T3 incentive: debit promotions_expense 500, credit provider_payable 500. T4 capture: debit consumer_receivable 2500, credit provider_payable 2000, credit platform_revenue 400.
  2. Append a duplicate of one T1 posting with the same idempotency_key, placed at the end of the stream, to exercise the dedupe path.
  3. Run the accumulator with debit = +1 and credit = -1: T1 gives +4000 -3200 -800 = 0; T2 gives -1000 +800 +200 = 0; T3 gives +500 -500 = 0; T4 gives +2500 -2000 -400 = +100.
  4. Shuffle the input and re-run to confirm the result is order-independent.
  5. Drop the duplicate row and re-run to confirm the output is unchanged.
EXPECTED RESULTExactly one transaction is reported: T4, with residual +100 cents. T1, T2 and T3 report clean, and the duplicated posting is counted once rather than pushing T1 to a residual of +4000 or -3200.
Follow-up
  • How do you catch a transaction that sums to zero but has the sign flipped on both legs, so money moved the wrong direction?
  • This must run nightly over a growing table without a full scan — how do you make it incremental when a chargeback can be posted today with an effective_at from three weeks ago?
  • What would you change if a single economic event legitimately spans two currencies?

Reorder a duplicated event stream under a memory cap

hard
deduplicationbufferingorderingbackpressure

A consumer reads outbox_event rows from an at-least-once bus: aggregate_type, aggregate_id, sequence_no, event_type, payload, where sequence_no is monotonic and gapless per aggregate. Delivery is out of order and duplicated, and the consumer may restart at any point. Apply each aggregate's events in sequence order, once each in effect, with total buffered events capped at 100,000 across all aggregates. Describe the data structures, the restart behaviour, and what you do when a gap does not close.

Approach
  1. Per aggregate keep next_expected plus a small map from sequence_no to the buffered event. Initialise next_expected from a durable checkpoint, never from 1: a consumer that starts mid-stream and assumes 1 will stall forever waiting for events the bus will not resend.
  2. Dispatch on the arriving sequence_no. Below next_expected is a duplicate or a replay and is dropped. Equal to next_expected is applied, then increment and drain the pending map while it contains the new next_expected. Above next_expected is buffered. Because you only ever probe the exact successor, the pending map is a hash map with O(1) amortised work per event; a heap would give O(log b) and buy nothing, since the minimum is never the question.
  3. Do not claim exactly-once over an at-least-once transport; what you build is idempotent application. The effect and the advance of next_expected must commit in one local transaction, or a crash between them re-applies on restart. Checkpointing every N events instead is a deliberate decision to re-apply up to N events, and is only safe when the effect is itself idempotent.
  4. Enforce the cap by counting buffered events globally. On reaching 100,000, stop acknowledging and let backpressure reach the bus rather than evicting, because an evicted event is lost permanently under a transport that will not redeliver what you acked. Where there is no backpressure, evict the aggregate holding the oldest buffered event and re-read its events straight from outbox_event by (aggregate_type, aggregate_id, sequence_no), which is unique and therefore an exact lookup.
  5. Resolve stuck gaps by reading the source, not by timing out. After a timeout, look up the missing sequence_no in outbox_event: if it is absent there, it was never written and skipping is correct; if it is present, the relay dropped it, so apply it and alert on the relay. Never advance next_expected over an unresolved gap on a timer alone.
  6. Summarise the bounds: O(1) amortised work per event, memory proportional to how far ahead the transport is allowed to run rather than to stream length. Per-aggregate ordering is the only ordering the bus offers, through partitioning on aggregate_id; there is no global order to reconstruct, and attempting one serialises the whole consumer.
Follow-up
  • One aggregate produces 60 percent of the buffered events — how do you keep it from starving every other aggregate out of the cap?
  • The relay is re-publishing a week of events after a bug; how does the consumer survive that without a stampede against the source table?
  • The effect is a call to a third-party API rather than a local write, so there is no shared transaction — what replaces the same-transaction dedupe?

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.

Small steps. Visible outcomes.0 / 7 completed
ONE WEEK · YOUR PACE

Prepare, practise & reflect

One practical outcome each day. Spend longer where you need it.

0 / 7 done
01Rebuild the primitives by implementing them
  • Implement a dynamic array with doubling growth and an operation counter, then change the growth rule to add a fixed sixteen slots instead, and time both for n of ten thousand, a hundred thousand and a million. The fixed-increment version resizes n/16 times at O(n) each, so its total work is quadratic; doubling is what makes append amortised constant.
  • Implement a hash map with separate chaining and a load-factor resize, then insert ten thousand keys engineered to land in one bucket and record what happens to lookup time, so that average-case O(1) becomes a claim with a stated precondition rather than a reflex.
  • For dynamic-array append and hash-map insert, write down which cost is amortised rather than worst-case, which single operation pays the whole bill, and what a system with a hard per-operation deadline would have to do instead.

Deliverable: Two working implementations plus a timing table showing the input at which each structure's advertised complexity stops holding.

Practice prompt ↗Practice prompt ↗Worked solution ↗
02Arrays under an invariant: two pointers, sliding window, binary search
  • Solve longest-subarray-with-sum-at-most-K using a sliding window, then run it on an input containing negative numbers and watch it return the wrong answer: extending the window only moves the sum monotonically when every element is non-negative, and that precondition is the whole reason the technique works.
  • Write the binary search that finds the first index satisfying a predicate rather than an exact value, put the loop invariant above the loop in a comment, and verify termination on the two inputs that break careless versions: the empty range, and a range where every element satisfies the predicate.
  • Compute the midpoint as lo + (hi - lo) / 2 and write one line on why the obvious (lo + hi) / 2 is a genuine defect in a fixed-width integer type and a non-issue in a language with arbitrary-precision integers.

Deliverable: Three solved problems, each with its invariant written above the loop, plus one recorded input on which the sliding window is provably wrong.

Practice prompt ↗Practice prompt ↗
03Sorting, heaps, and the greedy argument that has to be proved
  • Solve one top-k problem three ways, by full sort, by a size-k heap, and by quickselect, then write the values of n and k at which each becomes the right choice, along with quickselect's quadratic worst case and why a randomised pivot makes that unlikely rather than impossible.
  • Implement bottom-up heapify and count sift-down steps to confirm it does linear work rather than n log n, because most nodes sit near the bottom of the tree and therefore move only a short distance.
  • Take interval scheduling by earliest finishing time and write the exchange argument out in full: given any optimal schedule, swapping in the earliest-finishing interval keeps it feasible and no smaller. Then construct the weighted variant where that same greedy fails and name what has to replace it.

Deliverable: A three-way top-k comparison with measured crossover points, one written exchange argument, and one counterexample to a greedy rule that looks almost identical.

Practice prompt ↗Practice prompt ↗
04Recursion, memoisation, and the step to a table
  • Take one problem with overlapping subproblems, such as edit distance or coin change, instrument the plain recursion with a call counter to show the blow-up, then add memoisation and re-count.
  • Convert the memoised version to a bottom-up table and state the two properties you relied on: each subproblem's result depends only on its arguments, and the dependencies form a DAG you can enumerate in order.
  • Rewrite one deep recursion with an explicit stack, then find the input length at which the original hits the interpreter's frame limit, which defaults to about a thousand frames in CPython, so you know when the rewrite is required rather than decorative.

Deliverable: One problem in three forms, naive, memoised and tabulated, with call counts for each and the input length at which recursion depth becomes the binding constraint.

Practice prompt ↗Practice prompt ↗Worked solution ↗
05Graphs, where most of the work is choosing the traversal
  • Implement BFS and DFS over one adjacency list, then answer for each which finds a shortest path in an unweighted graph and which you would use to detect a cycle in a directed graph, including why the in-progress versus finished distinction matters for the second.
  • Implement topological sort by in-degree, feed it a graph containing a cycle, and confirm the failure signature is that fewer than V nodes come out rather than an exception, then note that the order it produces is one of several valid ones.
  • Run a shortest-path search on a graph with a single negative edge weight and show the wrong answer, then write the precondition Dijkstra actually needs, non-negative weights, because it finalises a node's distance the first time that node is popped, and name the algorithm you would switch to and its own limit.

Deliverable: A small graph library with BFS, DFS and topological sort, plus two inputs that produce documented wrong answers under the wrong algorithm choice.

Practice prompt ↗Practice prompt ↗
06One day for everything that is not an algorithm
  • Sketch one system only to the depth a coding-heavy loop tends to reach: the endpoints, what the service stores, and the single query pattern that decides the schema. Stop at twenty-five minutes.
  • Prepare the project answer for an interviewer who codes, which means rehearsing the two levels they push to: the specific thing you built, and why you chose that approach over the alternative they will name. Open with a number and be ready to say what it excludes.
  • Prepare the answer to what you would do differently, choosing a real technical mistake with a specific fix rather than a complaint about process or staffing.

Deliverable: One design sketch at endpoint-and-schema depth, plus a project answer rehearsed to two levels of follow-up.

Practice prompt ↗Practice prompt ↗
07Solve out loud, under time
  • Do three timed problems at twenty-five minutes each in a plain editor with no autocomplete and no execution until the end, then tally separately the failures that were syntax and the ones that were approach, because those two numbers call for different fixes.
  • Narrate one solution from the first sentence, stating the approach and its complexity before writing any code, and rehearse the sentence you will use when you realise mid-solution that the approach is wrong.
  • Re-solve from blank the two problems you were slowest on this week and compare the times against the day they first appeared.

Deliverable: A recording of one fully narrated solution and a tally that separates syntax failures from approach failures.

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.

Unblocking a stuck engineer without taking the keyboard

easy
mentoringleverageknowledge sharing

Describe a time you unblocked someone who had been stuck for more than a day. Say how you learned they were stuck, what you diagnosed the real blocker to be - missing context, a wrong mental model, a genuinely hard bug, or reluctance to ask - and what you actually did. State explicitly whether you took the keyboard and what that cost. Then say what changed so the next person is not stuck in the same place: a document, a test, a renamed function, a constraint that turns the mistake into an error.

Approach
  1. Diagnose the blocker type first, because the responses diverge sharply. Missing context is a five-minute fix; a wrong mental model has to be corrected out loud and checked; reluctance to ask is a team-norm problem you cannot solve in one sitting and should not pretend you did.
  2. Describe the intervention at the level of what you said or drew - the interleaving you sketched, the question that surfaced their hidden assumption - rather than the word 'pairing', which conveys nothing about what you contributed.
  3. Be explicit about taking the keyboard. It is sometimes right under time pressure and it always trades their learning for your speed. Naming the trade is the difference between mentoring and rescuing.
  4. Verify the unblock rather than assuming it: did they finish it alone, did they hit the same wall a fortnight later, did they later explain it to someone else. That last one is the strongest available evidence.
  5. Name the durable artifact. A mentoring answer with no residue describes one act of help; the leverage is in the test, the comment, or the constraint that makes the same confusion impossible next time.
Follow-up
  • How long did you let them struggle before stepping in, and how did you choose that duration?
  • What did you get wrong about why they were stuck?
  • When is taking the keyboard the correct call rather than the easy one?

Disclosing a money bug nobody has complained about yet

hard
escalationidempotencyremediation

You discover that a retry path has been double-capturing: rows in payment_attempt with status 'unknown' were retried with a freshly generated idempotency_key instead of the stored one, so some bookings carry two successful captures. No customer has complained. You have the ledger and the gateway's own records. Describe what you do, in what order, and whom you tell. State what you stop first, how you bound the affected set exactly, how corrections reach ledger_entry, and what you say to your manager if asked to hold the disclosure until the quarter closes.

Approach
  1. Stop the bleeding before measuring it. Every hour spent investigating with the retry path live adds rows to the set you will later have to refund, so the first action is a flag, a config change, or a revert - whichever reaches production fastest.
  2. Bound the set exactly rather than estimating. Group successful captures by booking_id having count(*) > 1, then confirm each candidate against the gateway's record, since the local table is the artefact you already know to be wrong. Produce a list of booking ids and a total amount, not a rate.
  3. Escalate in writing, immediately, with the number attached and a next-update time. A money defect has owners outside engineering, and the cost of support or finance hearing it from a cardholder instead of from you is out of all proportion to the delay you saved.
  4. Remediate with postings, never edits: one compensating refund posting per duplicated capture, idempotent per booking and duplicate attempt, leaving the original rows untouched so every historical report stays reproducible. Confirm each transaction_id still sums to zero.
  5. Close the class rather than the instance. Derive the idempotency key from (booking_id, kind, logical_attempt) and persist it before the call so a retry replays it byte-identically; add the reconciliation pass that resolves 'unknown' against the gateway instead of guessing; add an alert that fires on any booking with more than one succeeded capture.
  6. On the request to wait: say that you will not delay notification, offer to sequence the remediation around the close, and put the disagreement in writing. The refusal is quiet and specific, not a stand on principle.
Follow-up
  • The duplicate window overlaps a payout batch that already paid providers on the inflated amounts. What now?
  • How do you choose between proactively refunding everyone affected and waiting for disputes to arrive?
  • The gateway's record disagrees with your ledger for eleven bookings. Which one do you trust, and what do you do about the rest?

Walk an outage you owned from first signal to permanent fix

medium
incident responseblast radiuspostmortem

Pick an incident where you were the primary owner, not a helper. Tell it as a timeline: the first signal and what threshold fired it, what you ruled out and how, the mitigation you shipped and how long it took to reach production, and the permanent fix that followed. Quantify the blast radius in a countable unit - bookings affected, duplicate captures, minutes of degraded dispatch - and say how you bounded that number instead of extrapolating it. Close with the one change that made the class of failure impossible rather than merely unlikely.

Approach
  1. Lead with impact in one sentence - who was affected, for how long, in what unit - before any chronology, because that sentence is what the listener calibrates seniority against.
  2. Give the detection path honestly. 'A consumer emailed support' and 'the duplicate-capture alert fired at 14 per minute against a baseline of zero' describe very different systems, and claiming the second when it was the first collapses on the first follow-up.
  3. Run two clocks: time to mitigate and time to fix. Mitigation is whatever stops the bleeding within minutes (a flag, a rate cap, draining one partition); the fix is the structural change that lands days later. Collapsing them into one story hides whether you can triage under pressure.
  4. Bound the affected set with a query you can state, not a rate times a duration - for example successful captures grouped by booking_id having count(*) > 1 across the incident window, cross-checked against the gateway's own record. An exact list survives scrutiny; an estimate invites it.
  5. End on the structural change and say what it does not catch. A partial unique index on the live-offer status, an EXCLUDE constraint on overlapping reservations, or a guarded UPDATE whose affected-row count decides the winner each convert a silent corruption into a loud error, and each has a boundary worth naming.
Follow-up
  • What did you believe mid-incident that turned out to be wrong, and what made you drop it?
  • How did you verify impact had stopped, without relying on the alert clearing?
  • Who else on the team could have shipped the same bug that quarter, and what stops them now?
  • 01

    Describe a time you unblocked someone who had been stuck for more than a day. Say how you learned they were stuck, what you diagnosed the real blocker to be - missing context, a wrong mental model, a genuinely hard bug, or reluctance to ask - and what you actually did. State explicitly whether you took the keyboard and what that cost. Then say what changed so the next person is not stuck in the same place: a document, a test, a renamed function, a constraint that turns the mistake into an error.

  • 02

    You discover that a retry path has been double-capturing: rows in payment_attempt with status 'unknown' were retried with a freshly generated idempotency_key instead of the stored one, so some bookings carry two successful captures. No customer has complained. You have the ledger and the gateway's own records. Describe what you do, in what order, and whom you tell. State what you stop first, how you bound the affected set exactly, how corrections reach ledger_entry, and what you say to your manager if asked to hold the disclosure until the quarter closes.

  • 03

    Pick an incident where you were the primary owner, not a helper. Tell it as a timeline: the first signal and what threshold fired it, what you ruled out and how, the mitigation you shipped and how long it took to reach production, and the permanent fix that followed. Quantify the blast radius in a countable unit - bookings affected, duplicate captures, minutes of degraded dispatch - and say how you bounded that number instead of extrapolating it. Close with the one change that made the class of failure impossible rather than merely unlikely.

PracHub interview preparation framework
Is this an official Etsy interview guide?

No. It is PracHub's own research and practice material for the Software Engineer role at Etsy. Rounds and questions reflect what candidates have reported, not a process Etsy has published, and they change over time. Confirm the current format and scope with your recruiter.

PracHub interview research
What makes the Etsy technical interview process unique compared to other tech companies?

The process heavily emphasizes practical, real-world software engineering over theoretical puzzle-solving. Instead of traditional algorithmic LeetCode grinders, you will spend significant time debugging existing web applications in frameworks like Flask or Express.js, fixing broken test suites, and discussing real system architectures.

PracHub interview research
Can I complete the coding and debugging assessments in my language of choice?

While Etsy promotes a "language-agnostic" interview policy in theory, practical debugging screens and work samples frequently utilize standard web technologies such as JavaScript/Node.js, Python/Flask, or PHP. Clarify the exact environment with your recruiter beforehand to ensure you are comfortable navigating the provided IDE and stack syntax.

PracHub interview research
How important are the behavioral and cross-functional interview rounds?

Behavioral rounds are evaluated with equal weight alongside technical rounds. Etsy places strong cultural value on low-ego collaboration, clear communication with product managers and designers, and constructive problem-solving. Be prepared to share detailed, structured examples of past teamwork using the STAR format.

PracHub interview research
What is the typical timeframe for the complete interview process?

The entire process typically spans 3 to 5 weeks from initial recruiter outreach to final decision. Timeframes can vary based on team scheduling, holiday periods, or team-matching phases following successful technical loops.

PracHub interview research
Sources & methodology 3 sources ↗

Official role evidence, timestamped platform data and clearly labeled preparation advice.