As a Software Engineer at Retail Solutions, you play a pivotal role in shaping the technological landscape of the company. This position is essential for developing innovative solutions that enhance customer experiences and streamline operational processes. Your work will directly impact the effectiveness of various products and services, helping the company maintain its competitive edge in the retail industry.
In this role, you will contribute to diverse projects, ranging from backend system enhancements to frontend application design. You will collaborate with cross-functional teams, including product managers and UX designers, to create solutions that are not only functional but also user-friendly. The complexity of the systems you will be working on, combined with the scale of the user base, makes this role both challenging and rewarding. You will have the opportunity to influence key business strategies and drive significant improvements in the company's technology stack.
Initial Screening Call
reportedThe title covers product work, platform work, infrastructure, mobile and frontend, and those are different jobs with different loops behind them. A screening call is the cheapest place to find out which one the seat is, and asking reads as experienced rather than fussy. The questions that separate them: what the team is on call for, what the last three projects were, and whether any round happens inside an existing repository instead of a blank file. Then say which of that you have done and which you have not. Claiming the whole posting is the fastest way to be found out one round later.
What to demonstrate
- Whether you can locate your experience inside one flavour of the role honestly instead of claiming the entire requirements list
- Whether you name what you have not done, which an experienced screener reads as a level signal and can plan the loop around
- Whether what you want next matches what the seat is: someone who wants greenfield work landing on a team that mostly operates an existing system is a hire that leaves within the year
How to prepare
- Mark every line of the posting as done, adjacent or new, and write one sentence for each adjacent line naming the closest thing you actually built
- Split your last two years into rough percentages across feature work, operating and debugging live systems, and design or review, so a question about scope gets numbers rather than adjectives
- Bring three questions that discriminate between seats: what the team is paged for, how much of the work is changing existing code versus standing up something new, and what shipped in the last quarter
Technical Assessments
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
Onsite Interviews
reportedCoding rounds mostly set a floor. They decide whether you clear the bar, not where you land on the ladder. Level tends to come out of the design discussion and the ownership stories, so the question worth auditing beforehand is whether the scope you describe matches the scope of the job. Work that stops at your own service, or a story whose hard part was writing the code rather than getting several people to agree on an interface, reads a level below where you think you are interviewing, and that gap is usually resolved downwards.
What to demonstrate
- Whether the largest thing you describe owning ran end to end — the decision, the migration path, the rollout, and what you did when it went wrong — or stopped at the change you merged
- Whether design answers include what you would not build, what you would defer, and what you would measure before committing, rather than only what the boxes are
- Whether a disagreement in a story was settled with something checkable — a benchmark, a prototype, a written proposal — instead of by seniority or by waiting it out
- Whether you can say which calls you made alone and which you escalated, and why the line sat where it did
How to prepare
- Write your largest piece of owned work as a timeline of decisions — who decided what, when, and what you did when the plan broke — then delete every sentence whose subject is "we" and see how much survives
- Take one system you know well and drill the migration answer: how old and new paths run side by side under live traffic, how you compare their outputs, what the rollback is once writes are going to both, and which step you would not automate
- Map each line of the ladder in the job posting to a specific thing you have done, find the line you cannot support, and prepare the closest evidence you have plus an honest account of the gap
PracHub editorial advice for the preparation topics above.
Letting a flash sale converge on a single inventory row inside a long transaction
Every checkout for the same variant updates the same inventory_position row, and row-level locks serialize them: the ceiling is roughly one update per lock hold time, where hold time runs from lock acquisition to commit, not from the UPDATE statement. At a 2-5ms hold that is a few hundred updates per second on that key, and adding application instances only lengthens the queue of waiters while pinning connections. The amplifier is putting an external call inside the transaction — a 300ms payment authorization between the decrement and the COMMIT drops the ceiling to about three per second and takes the connection pool with it. The fixes each cost something concrete: sharding the counter into N sub-rows with random selection makes 'how many are left' a SUM over N rows and introduces false negatives when units strand in one shard; serialising per-variant through a single-writer partition removes contention but bounds latency by queue depth; an admission-control token bucket in front keeps the database healthy but must return a real waiting state to the customer rather than an error.
Applying order state changes as blind writes when events arrive at-least-once and out of order
A cancel request and a 'picked' event travel different paths with no shared ordering, and a message bus that guarantees at-least-once delivery will redeliver either one after a consumer crash. Read-modify-write handlers then let the later writer win arbitrarily, so you ship a cancelled order or refuse to cancel one still sitting on a shelf, and both outcomes are expensive. Every transition needs to be a compare-and-set on the expected current state with an explicit branch for zero affected rows, plus a processed-event table keyed on the producer's event id so that a redelivery is a no-op rather than a second application. Exactly-once delivery is not available over an unreliable network; at-least-once delivery combined with idempotent effects is, and conflating the two is what produces handlers that are correct only on the first run.
Writing code before the input contract is pinned down
Before the first line, state the types, the size bounds, whether duplicates, negatives or an empty input are possible, whether the input is sorted, whether you may mutate it, and what the function returns when nothing matches. Every one of those answers changes the code, and discovering one at minute twenty costs a rewrite you no longer have time for.
Not asking what the system looks like if it dies halfway through
For any multi-step write, say what state remains if the process stops between step two and step three, and what brings it back: a single transaction, a saga with compensating actions, an outbox, or a reconciliation job. Partial failure is routine at any real call volume, so 'that shouldn't happen' is an answer with nothing behind it.
Choose a category, try a prompt, then open its approach, worked solution or follow-up when you need it.
Describe how you would approach solving a problem using dynamic progra…
Describe how you would approach solving a problem using dynamic programming.
Approach
- State the target complexity and say which constraint rules the naive version out.
- Choose the data structure from the access pattern, not from familiarity.
- Name the brute-force solution and its complexity before improving on it.
Follow-up
- Which test case would catch an off-by-one here?
- How does this change if the input no longer fits in memory?
Explain the difference between breadth-first search and depth-first se…
Explain the difference between breadth-first search and depth-first search.
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?
Write a function to reverse a string in Java.
Write a function to reverse a string in Java.
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.
- Choose the data structure from the access pattern, not from familiarity.
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 an array of integers, find two numbers that add up to a specific…
Given an array of integers, find two numbers that add up to a specific target.
Approach
- Choose the data structure from the access pattern, not from familiarity.
- Walk one small example through your approach before writing the whole thing.
- 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?
- Which test case would catch an off-by-one here?
Roll up facet counts over a category graph without double counting
Categories form a directed acyclic graph of up to 100,000 nodes and 300,000 child-to-parent edges, and a category may have several parents. Two million active variants each sit in exactly one category. For every category, return the number of distinct active variants in it or any descendant, and refuse to produce counts at all if a supplier feed has introduced a cycle. The memory budget is one gigabyte. State your complexity and where exactness is lost, if it is.
Approach
- Run Kahn's algorithm first: repeatedly remove zero-in-degree nodes, and if any node remains the graph contains a cycle. It costs O(V+E) and hands you the topological order the rollup needs anyway. Report the residual node set so the feed owner sees which edges close the cycle, rather than an assertion that the feed is bad.
- Show why addition is wrong here. On a tree, counts accumulate exactly in reverse topological order. On a DAG, a category reachable from an ancestor by two paths contributes twice, so a straight sum overstates every node above a diamond — and the overstatement is largest at the high-traffic parent categories, which is where a wrong number is most visible.
- Exact distinct counting needs set union. A bitset per node is 2,000,000 bits, or 250 KB, and 100,000 nodes is 25 GB — twenty-five times the budget. Compute that number and abandon the approach explicitly instead of hand-waving past it.
- Use HyperLogLog and merge in reverse topological order. Union is lossless because it is the register-wise maximum, which is exactly the property that makes it safe on a DAG where one variant arrives by two paths. At m = 4,096 registers the relative standard error is 1.04/sqrt(m), about 1.6%, at roughly 4 KB per sketch — about 400 MB for 100,000 nodes, inside budget. At m = 1,024 it is 3.25% error and about 100 MB.
- Complexity: O(V + E) traversal with one sketch merge per edge, O(V*m) space. Then state the exactness policy rather than leaving it implicit: keep exact sets below a cardinality threshold and switch to sketches above it, because the pages where an off-by-a-few count is noticeable are the small ones.
Worked solution 35 min
- Build a 12-node DAG containing one diamond, assign variants to leaves by hand, and write down the exact per-node distinct counts.
- Implement the additive rollup and confirm it overstates at and above the diamond by exactly the shared subtree's variant count.
- Implement the HLL rollup at m = 4,096 and compare against exact counts on the toy graph and on a generated 100,000-node graph.
- Add an edge that closes a cycle and confirm the job refuses with the residual node list rather than looping or emitting partial counts.
Follow-up
- A facet count and the filtered result count differ by 1.4%. What do you show the customer, and which of the two numbers do you fix?
- One variant moves between categories. What must be recomputed, and can it be done incrementally?
- Counts must now exclude variants with zero ATP at every node. Where does that predicate live, and what does it do to your refresh cadence?
Retire a variant without breaking historical order lines
product_variant has status in draft, active, discontinued, blocked, and sku_code UNIQUE. order_line references variant_id and already stores title_snapshot and unit_price_minor. A team proposes adding deleted_at to product_variant, hard-deleting anything discontinued for a year, and reusing the freed sku_code values. Say what each of those three does to order_line and to the unique constraint, give the DDL you would actually ship including any audit table, and write the query listing unshipped lines on confirmed orders whose variant is no longer active.
Approach
- Kill the hard delete on referential grounds rather than on principle. order_line.variant_id is a foreign key, so a DELETE either fails under the default NO ACTION or, with ON DELETE CASCADE, silently removes paid order history. Neither of those is a retention policy.
- Point at title_snapshot and unit_price_minor: that denormalisation is exactly what makes retirement survivable, because rendering an old order never joins product_variant. The row then only has to persist for the foreign key and for analytics, not for display, which is a much weaker requirement than the team assumed.
- Prefer the status enum that already exists over a parallel deleted_at. Two independent ways for a row to be dead means every query needs both predicates, and the one somebody forgets is the one that resurfaces a blocked variant in a facet or a sitemap.
- If soft delete ships anyway, the unique constraint must become a partial unique index — UNIQUE (sku_code) WHERE deleted_at IS NULL — or a retired row holds the code forever. Then argue against reuse on its merits: sku_code is the warehouse- and supplier-facing identifier, so reusing it makes every historical pick, return and supplier invoice ambiguous about which product it refers to.
- Put history in product_variant_audit at one row per change, keyed (variant_id, version), carrying the changed columns and changed_at_utc, written by the single writer or a trigger. For as-of questions either keep valid_from_utc and valid_to_utc with an EXCLUDE constraint on (variant_id WITH =, validity WITH &&) using btree_gist to forbid overlaps, or reconstruct by taking the newest audit row at or before the timestamp — the first costs a write-side constraint, the second costs a per-query sort.
Worked solution 25 min
- Attempt a DELETE of a variant referenced by order_line and record the exact error and the constraint name it reports.
- Add deleted_at, mark one variant deleted, and try to insert a new variant reusing its sku_code; record the unique violation.
- Replace UNIQUE (sku_code) with a partial unique index WHERE deleted_at IS NULL and repeat the insert.
- Write and run the unshipped-lines query joining order_line to customer_order and product_variant, filtering order_line.state in ('reserved','released_to_node') and product_variant.status <> 'active'.
- Mark a variant discontinued rather than deleted and confirm the query still returns its lines.
Follow-up
- A returns agent needs the variant's hazmat flag as it stood on the shipment date. Which of the two history shapes answers that in one index seek, and what does the other cost?
- An erasure request arrives for a customer, not a variant. Which columns on customer_order and order_line can be erased while the financial record stays reconcilable?
- Merchandising wants discontinued variants gone from search within a minute. What event do you publish, and what does the consumer do with it?
Write the reserve statement that cannot oversell a variant
inventory_position, keyed (variant_id, node_id), holds on_hand_units, reserved_units, damaged_units, safety_stock_units, oversell_allowance_units and CHECK (reserved_units <= on_hand_units - damaged_units + oversell_allowance_units). inventory_reservation holds reservation_id, variant_id, node_id, owner_type, owner_id, units, state, idempotency_key UNIQUE. Write the exact statements one reserve call runs, name the isolation level, and say what a retry of the same call does. Then explain why SELECT the position, subtract in application code, UPDATE to the result loses units, and give the throughput ceiling for one contended row.
Approach
- Write available-to-promise as an expression, never a column: on_hand_units - damaged_units - reserved_units - safety_stock_units + oversell_allowance_units, with in_transit_units included only when the promise date is beyond expected_receipt_at_utc. Note that the CHECK constraint is deliberately looser than that policy because it ignores safety stock: the constraint is the hard oversell floor, the WHERE clause is the commercial one, and they are allowed to differ.
- Claim idempotency first. INSERT INTO inventory_reservation (...) VALUES (...) ON CONFLICT (idempotency_key) DO NOTHING RETURNING reservation_id. Zero rows returned means this call is a replay, so read the existing row, return it, and touch no counter. Doing this before the counter update is what makes a retried reserve a no-op instead of a second hold.
- Bind the units in one statement: UPDATE inventory_position SET reserved_units = reserved_units + $n, updated_at_utc = now() WHERE variant_id = $v AND node_id = $k AND on_hand_units - damaged_units - reserved_units - safety_stock_units + oversell_allowance_units >= $n. Judge it by rows affected; zero is a refusal, and rolling the transaction back drops the reservation row so the caller may genuinely retry later — a refusal spends nothing.
- Say why that is safe at READ COMMITTED in PostgreSQL: the UPDATE blocks on the row lock and, when the blocking transaction commits, re-evaluates its WHERE clause against the updated row version rather than against the snapshot it began with. Split into SELECT then UPDATE and the two statements take separate snapshots, both read the same available count, and both write — a lost update that READ COMMITTED permits by design. REPEATABLE READ does not silently fix it; it converts it into a serialization failure (SQLSTATE 40001) that somebody has to retry, and InnoDB's REPEATABLE READ does not abort at all, so identical code behaves differently on a different engine.
- Give the ceiling as arithmetic. The row lock is held from the UPDATE until COMMIT, so a 2 ms hold caps that one key near 500 commits per second and a 5 ms hold near 200, regardless of how many application instances exist — extra instances only lengthen the waiter queue and pin connections. Put a 300 ms payment authorization inside the same transaction and the ceiling falls to roughly three per second, which is the single most common way this design is destroyed.
- Price each mitigation rather than listing them: sharding one row into N sub-rows makes remaining stock a SUM over N and strands units in shards nobody selects; a single-writer partition per variant removes contention but bounds latency by queue depth; admission control protects the database only if the customer is shown a real waiting state instead of an error.
Follow-up
- The expiry sweeper releases a hold at the same instant the order service commits it. Write both statements so a committed reservation can never be expired.
- reserved_units has drifted 40 units above the sum of active reservations on one row. Write the reconciliation query, and say what it should do when it finds that at 3am.
- Two nodes can both satisfy the line. Where does node selection happen, and what does it do to your single-row contention argument?
How do you ensure data security in your applications?
How do you ensure data security in your applications?
Approach
- Name the failure you are designing for, then the recovery path.
- State the consistency you need, and where you are willing to be stale.
- Fix the scope first: who calls this, how often, and what they do when it fails.
Follow-up
- What would you drop to keep the system up under load?
- What breaks first when traffic grows ten times?
How would you architect a real-time chat application?
How would you architect a real-time chat application?
Approach
- Fix the scope first: who calls this, how often, and what they do when it fails.
- Name the read and write paths separately; they rarely have the same bottleneck.
- Name the failure you are designing for, then the recovery path.
Follow-up
- How does this behave when that dependency is down for an hour?
- What breaks first when traffic grows ten times?
A customer reports a bug in one of our applications. How would you app…
A customer reports a bug in one of our applications. How would you approach troubleshooting it?
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.
- State your assumptions explicitly before working the problem.
Follow-up
- What assumption would you test first?
- How would you know your answer was wrong?
Evolve the order representation without breaking installed clients
GET /orders/{id} is consumed by a storefront you deploy, a mobile app with a long tail of versions you cannot force to upgrade, and partner integrations you do not control. Three changes are queued: order_line.state gains a new value, grand_total_minor is replaced by a money object carrying amount and currency, and the line array must become paginated for a four-hundred-line order. Define the versioning scheme, the compatibility rules inside a version, and the migration for each of the three. Include how you establish that the last old client is gone.
Approach
- Classify the three changes, because they are not the same kind of change. A new enum value breaks any client that switches exhaustively, and whether it breaks is decided by a tolerance rule you either published on day one or cannot retrofit. Replacing a scalar with an object is unambiguously breaking. Paginating an array breaks anyone who reads the array's length, which is everyone who ever summed it.
- Choose the versioning axis and accept its cost. A version in the path is trivially routable and cacheable but forks handlers and invites duplication; a dated version in a request header pins each client to a snapshot and lets one handler serve many, at the cost of a transformation chain that needs testing per version pair. The rule that matters more than the choice: inside a version, additive only. New optional fields yes; removals, type changes, and newly tightened validation no.
- Ship the money change additively. Emit both grand_total_minor and total: {amount_minor, currency_code} for the whole deprecation window, mark the old field deprecated in the published schema, and derive one from the other at exactly one point so the two cannot disagree during the overlap.
- Ship pagination as a new representation rather than a mutation of the old one: keep lines inline with a documented cap in the existing version, add a lines sub-resource with its own cursor, and add a lines_truncated boolean so an old client can at least detect the case it cannot handle. Silently returning a truncated array is worse than any error, because the client computes a wrong total and reports it to a customer.
- Ship the enum value against the tolerance rule if one exists. If it does not, the honest options are a version bump, or mapping the new state onto the nearest old value for old clients while stating which lie you are telling: mapping on_hold_fraud onto pending_payment keeps old clients out of the shipped path, which is the safe direction to be wrong in.
- Instrument the retirement, and be clear about what you cannot see: the server observes requests, never which response fields a client reads, so a per-client-version request counter keyed on a required client identifier is a proxy and not proof. Pair it with Deprecation and Sunset (RFC 8594) headers carrying the retirement date and a rehearsal in which a sampled slice of old-version traffic receives 410 for an hour, then complete the removal.
Worked solution 35 min
- Build the compatibility matrix: for each of the three changes, name which client breaks and at which line of its own code.
- Choose the versioning axis and write the single paragraph of contract text that states the additive-only rule inside a version.
- Write the dual-field emission for money and name the one place the derivation happens.
- Write the pagination migration including the truncation flag and the new sub-resource, and note when the flag has to ship relative to the first oversized order.
- Write the retirement plan: the client identifier, the counter, the response headers, the sampled dark period, and the date.
Follow-up
- A partner pins to a dated version from two years ago and never moves. What is the enforcement mechanism, as opposed to the policy?
- How do you keep transformation chains for five old versions tested without maintaining five copies of every test?
- One client fetches an order and PUTs the representation back. What happens to the fields its version does not know about, and does PATCH actually fix it?
Order history page slows as its line count grows
The account order-history endpoint returns a customer's last 20 customer_order rows with their order_line rows, and for each line renders a display title read from product_variant plus tracking read from shipment. p95 is 380 ms; the single-order view is 40 ms. The database logs 157 statements for one request, none slower than 1 ms, with flat CPU and no bad plans. Diagnose the latency, name the second defect the same code path carries, give the fix, and state the statement count you expect afterwards.
Approach
- Read the shape out of the count before forming a theory. Twenty orders averaging 3.4 lines gives 68 lines, and 1 + 20 + 68 + 68 = 157: one driver query, one per order, two per line. Every statement being sub-millisecond rules out plans, locks and data volume, so the time is round trips.
- Check the arithmetic accounts for the whole regression before fixing anything. 156 extra round trips at roughly 2 ms each is about 310 ms on top of a 40 ms baseline, which covers the observed 380 ms. Had it covered only half, there would be a second latency defect and the batching fix alone would disappoint.
- Identify the per-row statements by their normalised text: a lines-by-order_id lookup, a single-row product_variant lookup keyed by variant_id, and a single-row shipment lookup keyed by shipment_id. Confirm they are lazy relationship loads by dropping those two fields from the response and watching the count fall to 21.
- Rewrite the round trips away first, one batched statement per level. Select the orders, then SELECT ... FROM order_line WHERE order_id = ANY($1), then SELECT ... FROM product_variant WHERE variant_id = ANY($1), then SELECT ... FROM shipment WHERE shipment_id = ANY($1), and join them into maps in application memory. That is four statements, down from 157, and the count is now independent of both orders and lines per order.
- Name the correctness defect the batching accidentally exposes: the display title is being joined back to product_variant, which is mutable and carries no validity interval, so a renamed or discontinued variant silently rewrites a historical order. order_line.title_snapshot and unit_price_minor exist precisely so the order does not read the live catalogue. Read the snapshot and the product_variant batch disappears entirely, leaving three statements: the orders, their lines, and the shipments.
Follow-up
- The endpoint filters on customer_id. What happens to guest orders, which carry customer_id NULL and are identified by contact_email?
- A wholesale account has 4,000 orders. What breaks about ANY($1) and keyset pagination here, and in which order do you fix them?
- No statement was slow, so no slow-query alert fired. What signal would have caught this before a customer did?
For someone fluent in a dynamic language who has shipped real work but has never had to say what the runtime is doing underneath. The week is built on measuring and deliberately breaking things, because the questions that expose this background are the ones where the interviewer asks why a second time.
Prepare, practise & reflect
One practical outcome each day. Spend longer where you need it.
0 / 7 done01Measure before reasoning
- Take a slow piece of your own code, write down in advance where you believe the time goes, then profile it and record how wrong the guess was. The cost is usually an allocation you did not notice or an accidental quadratic membership test.
- Replace one list membership test inside a loop with a set and measure at a thousand, ten thousand and a hundred thousand elements, confirming the shape of the curve rather than only that it got faster.
- Write down the three quantities you can now measure instead of assert: wall time, peak memory, and call count for the function you suspected.
Deliverable: A before-and-after profile of real code plus a written note on the size of the gap between the guess and the measurement.
Practice prompt ↗Practice prompt ↗Practice prompt ↗Worked solution ↗02References, copies, and the bugs they produce
- Write the function with a mutable default argument, call it three times, and explain the accumulating result: the default is evaluated once when the function is defined, so every call shares one object.
- Build a nested structure, take a shallow copy, mutate an inner element, and show that both views changed, because a shallow copy duplicates the container and not the elements. Then fix it with a deep copy and state the cost you just accepted.
- Write two functions, one mutating its argument in place and one rebinding the local name, and predict the caller's view of each before running it. That single distinction produces most of the bugs that pass their tests.
Deliverable: Three small programs whose output you predicted correctly before running, each with a one-line statement of the rule underneath.
Practice prompt ↗Practice prompt ↗03Types, once, in a language that checks them
- Port one module you have already written, roughly a hundred lines, into a statically typed language, and record every place the compiler demanded an answer your original had left implicit: a value that can be absent, a numeric width, a case never handled.
- Write the same signature in both languages and state what the static one guarantees before the program runs and what it does not, since it will not save you from a wrong algorithm or an index out of range.
- Write the difference between an interface satisfied by declaration and one satisfied structurally, with one case each where the other approach would miss the mistake.
Deliverable: One module in two languages plus a list of the questions the type checker forced you to answer.
Practice prompt ↗Practice prompt ↗04Concurrency, starting with what actually runs at the same time
- Run the same CPU-bound function across four threads and four processes and measure both. Under the default CPython build the threaded version will not speed up, because only one thread executes bytecode at a time; the process version will. Check which build you are on first, since free-threaded builds remove that lock and change the result.
- Then run a blocking I/O workload across four threads and measure it speeding up, because the interpreter releases that lock around blocking calls, which is why treating threads as useless is wrong as a general claim.
- Build the lost update: two threads each incrementing a shared counter a hundred thousand times, and show a final value below the expected sum, because an increment is a load, an add and a store and the thread can be suspended between them. Fix it with a lock and then measure what the lock costs.
Deliverable: Three measurements, threads against processes on CPU work, threads on I/O work, and a demonstrated lost update, each with the mechanism written underneath.
Practice prompt ↗Practice prompt ↗Worked solution ↗05Debugging as a procedure rather than an instinct
- Work one real failure as a bisection: find a revision or an input size where it is good and one where it is bad, halve repeatedly, and state the two assumptions bisection needs, that the property changes exactly once across the range and that the test is reliable.
- Minimise one failing input to the smallest version that still fails, and record how many rounds it took.
- Keep a hypothesis log for one bug in three columns, what I believe, what would disprove it, what I observed, and stop yourself the first time you are about to change two things at once.
Deliverable: One bug worked to root cause with a written hypothesis log and a minimised reproducing input.
Practice prompt ↗Practice prompt ↗06Tests that catch the bug you are about to write
- Implement an LRU cache with a capacity bound, then write the three test cases that would catch an off-by-one in eviction: insert exactly capacity items and assert nothing was evicted, insert one more and assert the least recently used key is the one gone, and read an old key just before that insert so the eviction victim changes.
- Add a property test comparing your implementation against a deliberately slow reference, an ordered list scanned linearly, over a few thousand random operation sequences, because a slow reference finds the cases you would not have thought to write.
- Write one numeric test that fails under exact equality and passes with a tolerance, and state why the tolerance has to be relative rather than absolute once the magnitudes grow.
Deliverable: An LRU implementation with three boundary tests, one property test against a slow reference, and one tolerance-based numeric test.
Practice prompt ↗Practice prompt ↗07Debug something broken, out loud
- Have someone plant three defects in a two-hundred-line program, an off-by-one, a shared mutable state bug, and a wrong error-handling path, then find them while narrating, under a fixed rule: state the hypothesis before touching anything.
- Time each one and record which tool found it, reading, a printed value, a debugger, or a test, because the question asked in interviews is how you would find it rather than what it was.
- Write the sentence you will use when you do not yet know the cause, one that names the next measurement instead of offering a guess.
Deliverable: A recorded debugging session with time-to-find per defect and the method that found each.
Practice prompt ↗Practice prompt ↗Worked solution ↗Expand any day for tasks and deliverables. Your progress is saved on this device.
Every story you tell gets read for blast radius and judgement: what could have broken, who else it touched, what you knew at the moment you decided. Nobody can audit your code in an hour, so they audit your reasoning instead. Pick work where the call was genuinely yours and the consequences were real enough to remember.
Can you provide an example of how you have handled a conflict within a…
Can you provide an example of how you have handled a conflict within a team?
Approach
- Name the disagreement and how you resolved it with evidence.
- Pick a story where you made the decision, not one where you watched it.
- Close with what you would do differently, concretely.
Follow-up
- What would you do differently if you ran that again?
- How did you know your change caused the improvement?
What motivates you to achieve your goals as a software engineer?
What motivates you to achieve your goals as a software engineer?
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
- How did you know your change caused the improvement?
- What would you do differently if you ran that again?
Argue against holding reservations in a cache with a TTL
A lead specifies that checkout should hold reservations in an in-memory cache with a fifteen-minute TTL and write back to inventory_position asynchronously, on the grounds that the database row is the bottleneck. You believe it is wrong and you have been assigned to build it. Describe a time you argued against a design you were told to implement. State the failure you predicted, the evidence you brought, how long the disagreement ran, what you did once the decision went against you, and what production eventually showed. Include the version of your argument that persuaded nobody.
Approach
- Establish the failure precisely, and note that there are two independent ones. First, replication in a typical in-memory store is asynchronous, so a failover can promote a replica that is missing writes already acknowledged to the client, and here those lost writes are holds on units customers are mid-payment for. Second, and separately, a TTL that expires while an authorization is in flight releases units to another buyer, producing an oversell that no component logs as an error.
- Pre-empt the durability counter-argument, because that is what keeps the design alive: an append-only file flushed once a second bounds loss on a single node to roughly a second of writes and says nothing about what a failover discards. Durability knobs on a single node do not make a replicated cache a transactional store.
- Attack the premise rather than the taste, because the premise was a performance claim and is therefore measurable. The claimed bottleneck is one row's lock hold time, which runs from lock acquisition to commit: at a 2-5ms hold that is a few hundred updates per second on that key, and the number collapses only if something slow sits inside the transaction. Measure the current hold time and find out whether the external call is inside it before redesigning around the symptom.
- Bring evidence the decision-maker can check in a day, not a quarter: the measured p99 hold time on the hot row, and a count of committed reservation units exceeding the position for any variant over the last week. An argument that costs the other person one query is the one that moves.
- State the alternative in one sentence with its cost owned: the cache is a negative filter that may say 'definitely none left' and never 'yes, it is yours', the binding decision is a conditional UPDATE in the same store as the reservation rows judged by affected-row count, and the cost is that you now confront the real single-key ceiling and must pay for sharding, single-writer partitioning or admission control if it binds.
- Describe the disagree-and-commit mechanics concretely: what you built, what you instrumented so the prediction was falsifiable, and what threshold would have proved you wrong. Report the outcome including the chance you overstated severity, and keep 'I was right' separate from 'the disagreement was handled well'.
Follow-up
- You lost the argument. What do you instrument so the question is settled by data in a month rather than by another meeting?
- Suppose the measurement shows the row genuinely tops out below the drop's arrival rate. Which mitigation do you pick, and what does it break?
- What evidence would have made you drop the objection entirely?
- 01
Can you provide an example of how you have handled a conflict within a team?
- 02
What motivates you to achieve your goals as a software engineer?
- 03
A lead specifies that checkout should hold reservations in an in-memory cache with a fifteen-minute TTL and write back to inventory_position asynchronously, on the grounds that the database row is the bottleneck. You believe it is wrong and you have been assigned to build it. Describe a time you argued against a design you were told to implement. State the failure you predicted, the evidence you brought, how long the disagreement ran, what you did once the decision went against you, and what production eventually showed. Include the version of your argument that persuaded nobody.
Is this an official Retail Solutions interview guide?
No. It is PracHub's own research and practice material for the Software Engineer role at Retail Solutions. Rounds and questions reflect what candidates have reported, not a process Retail Solutions has published, and they change over time. Confirm the current format and scope with your recruiter.
PracHub interview research ↗What is the typical interview difficulty and preparation time?
The interviews are generally rigorous, with a mix of technical and behavioral questions. Candidates often find that dedicating several weeks to preparation can significantly improve their performance.
PracHub interview research ↗How can I differentiate myself as a candidate?
Successful candidates often demonstrate a strong grasp of technical concepts and the ability to articulate their thought processes clearly. Additionally, showcasing a collaborative mindset and alignment with the company's values can set you apart.
PracHub interview research ↗What is the culture like at Retail Solutions?
The culture at Retail Solutions values innovation, collaboration, and continuous improvement. Engineers are encouraged to share ideas and work together to solve complex problems.
PracHub interview research ↗What is the typical timeline from initial screen to offer?
The entire interview process can take several weeks, with candidates usually receiving feedback after each stage, allowing them to adjust their preparation accordingly.
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