Interview guides · ALT Sales
ALT Sales Software Engineer Interview Guide
ALT Sales Software Engineer interview guide: take-home and code review prep, system design for trading and transaction data, and behavioral stories.
The 60-second brief
Candidates do not describe the ALT Sales product in detail. A candidate review mentions transforming a class of assets. The reported design scenarios involve real-time bidding or trading, storing user transaction histories, caching the main product feed and pushing frequent changes in asset valuations to active users. Candidates describe the Software Engineer role as building and maintaining backend services and features, from requirement to deployment, with product, design and operations colleagues. They name the Head of Engineering and the Head of Operations as people they meet. The requirements candidates describe are fluency in a backend or full-stack language (Python, Node.js, Go or Ruby, depending on the team), relational databases, API design and Git. The same requirements list AWS or GCP, Docker or Kubernetes, and a background in marketplaces, trading platforms or alternative assets as a plus.
This guide is for backend and full-stack engineers interviewing for a Software Engineer role at ALT Sales. It covers the four reported rounds and how to build the take-home so you can defend and extend it later. It works through the reported design questions: bidding or trading, secure transaction history storage, feed caching, monolith versus microservices, and zero-downtime database migrations. It also covers the behavioral questions candidates report from engineering and operations leadership. Candidates report that the design round may go deeper for more senior candidates, so senior candidates should give the design days more weight.
Candidates report four rounds over about 3-5 weeks: Recruiter Call, Technical Screen, Take-Home Assignment and a Final Interview Loop. The final loop combines a review of your take-home code, a system design interview and conversations with engineering and operations leadership.
Focus areas
- System design
- Take-home assignments
- Code review
Overview
The topics candidates list for this loop are system design, take-home assignments, code review, live coding and programming fundamentals. Candidates report the take-home and the code review as the main parts of the technical evaluation. You build a small project in the company's domain, and the final loop includes a session where you walk a senior engineer through it. The reported review questions ask you to justify your choices and extend the code. That means the take-home gives you material for two rounds. Every shortcut you take while building it becomes a question you answer out loud later.
The reported design questions point to a financial or asset-trading domain: a real-time bidding or trading backend, secure storage and retrieval of transaction histories, and caching for a product feed. Two architecture questions round them out: monolith versus microservices, and database migrations with no downtime. In practice that means a small set of techniques: concurrency control on a contested record (conditional updates or one writer per auction), append-only ledgers that store money as integer minor units, keyset pagination backed by an index that matches the sort order, cache-aside with invalidation and stampede protection, and expand/contract schema changes. The requirements candidates describe stress relational databases and API design, so plan on SQL and HTTP contracts in your answers more than exotic infrastructure.
Candidates report that the behavioral questions often come from the Head of Engineering or the Head of Operations. The reported tip for the operations conversation is to frame past work by its business impact. Prepare each story with a number an operations leader would track, such as hours saved, errors avoided or revenue affected. Prepare a specific, researched answer to why ALT Sales, because candidates report that question directly.
The ALT Sales interview process
These are the four rounds candidates report. The take-home ties the third and fourth rounds together because the final loop includes a review of your code, so prepare for them as one block.
Recruiter Call
Candidates describe this as an introductory call with a recruiter that checks high-level fit with the team. No technical questions are reported for it. Use it to get the facts you need for the later stages. Ask which language and stack the team uses, since the requirements candidates describe range across Python, Node.js, Go and Ruby. Ask what form the take-home takes and how much freedom you have in language and framework. Ask who you will meet in the final loop. Have a short, concrete answer ready for why this company, because candidates report that question in the behavioral set.
What to demonstrate
- High-level fit with the team, which is the purpose candidates report for this call
- How clearly you can summarise your backend or full-stack experience
- Whether your interest in the company and its domain is specific
How to prepare
- Write a short summary of your background that leads with one shipped backend feature and its measured effect.
- Write your answer to why ALT Sales, using what you have researched about its product and the asset or marketplace domain.
- List your questions for the recruiter: team stack, take-home format and language freedom, and who sits in the final loop.
Technical Screen
Candidates describe the Technical Screen only briefly, as the first technical evaluation, and link it to the take-home assignment. No specific questions are reported for this stage. Live coding and programming fundamentals appear among the topics candidates list for the loop, so be ready to write working code in your main language and to talk through recent technical work in detail. If the take-home is handed out or explained here, confirm the scope and the submission requirements before you hang up.
What to demonstrate
- Your basic technical footing, as the stage's name and reported description suggest
- Your readiness to take on the take-home that candidates link to this stage
How to prepare
- Warm up in your chosen language with short coding drills, such as the overlapping-intervals and rolling failure-rate practice problems, and narrate as you write.
- Prepare an account of one recent system you built: its data model, its API and one trade-off you would revisit.
- Write down the questions to ask about the take-home: expected scope, required stack, submission format and how it will be reviewed.
Take-Home Assignment
Candidates describe a small project tied to the company's domain that lets you show practical engineering. The reported preparation advice is to treat it like a work ticket you plan to merge. Include a README that says how to run it and what you assumed, tests you wrote yourself, deliberate handling of bad input, and a clear structure. Candidates list CI and containerization as less common extras. Build it knowing it comes back in the final loop. Keep a short list of the decisions you made and the alternatives you rejected. Leave the code in a state where adding an endpoint is easy, since adding one is a reported review question.
What to demonstrate
- Code structure and readability: file layout, naming and separation of concerns
- Tests and edge-case handling
- Trade-offs and technical debt you can name and explain
How to prepare
- Build a practice version first: a small REST API in your stack backed by a relational database, with a README, unit tests and a consistent error format.
- Write a short README section on assumptions, trade-offs and what you would do next, including what would break at 100x traffic.
- Add structured logging and a central error handler so the error-handling question has a concrete answer in your own code.
Final Interview Loop
Candidates report that the final loop includes a deep review of the take-home with a senior engineer, a dedicated system design interview, and conversations with engineering and operations leadership. They also report that once the take-home is done, the final loop is often scheduled and finished within a single week. For the review, re-read your submission as a stranger would. Have answers ready for the hardest part, your data structure choices, error handling and scaling, and be ready to add an endpoint live. For design, rehearse the reported scenarios and ask about read/write ratios, user scale and latency before drawing anything. Candidates also report that design depth may scale with seniority. For leadership, prepare stories whose results are measured in business terms.
What to demonstrate
- Whether you can defend and extend your own take-home code
- System design: API and data modelling, scaling with caching and asynchronous work, and trade-offs such as SQL versus NoSQL or consistency versus availability
- Communication and cross-functional work in the conversations with leadership
How to prepare
- Annotate your submission: three decisions with the alternatives you rejected, and one piece of code you would rewrite and how.
- Rehearse the bidding or trading design, the transaction-history design and the feed-caching design end to end, each with a data model and its failure modes.
- Prepare five behavioral stories that together cover disagreement, cross-team work, feedback, turning a project around and pushing back on a requirement.
Where candidates lose points
Submitting a take-home the reviewer cannot run or trust: no README, no tests, and errors that crash the app.
Before submitting, clone the repository into a clean directory and follow your own README from scratch. Include setup and run steps, your assumptions, the trade-offs you made and what you would do next. Write your own tests for the happy path and for bad input, and return a consistent error format instead of stack traces.
Being unable to explain your own code in the review, with vague answers about why you chose a data structure, library or pattern.
Annotate your submission before the final loop. For each major choice, write down the alternative you rejected and why. Rehearse the hardest-part, data-structure, error-handling and 100x-traffic answers against your actual code, and practise adding an endpoint to it while talking.
Designing the bidding or trading backend with no concurrency control, so two simultaneous bids can both be accepted or overwrite each other.
Make the accept step atomic: a conditional update with a version or price check, a row lock, or a single writer per auction or instrument. Put idempotency keys on submission, and explain how ties and bids that arrive at close are resolved.
Treating transaction history as ordinary mutable rows: float amounts, in-place edits and OFFSET pagination.
Store amounts as integer minor units or DECIMAL, record corrections as reversing entries in an append-only ledger, and paginate with a keyset on an index of (user_id, created_at DESC, id DESC). Name the access-control check that stops one user from reading another user's history.
Answering the leadership conversations with technical detail only, without saying who benefited or by how much.
Candidates report that the operations conversation focuses on business impact. Give each story a result an operations or business leader would track, and have a researched answer to why ALT Sales plus questions about the roadmap and team.
Questions & practice
Find overlapping job attempts and peak concurrency from lease records
Medium · sweep line · intervals · leases
A day of job_run history yields about 50,000,000 attempt records: (job_run_id, job_type, attempt, started_at, finished_at which is NULL when the worker died, lease_expires_at). Leases expire on a clock, so a job that outran its lease ran twice. Produce (a) every job_run_id whose attempts overlapped in wall-clock time and (b) the peak number of simultaneously running attempts per job_type with the minute it occurred. Target O(n log n). State how you treat a NULL finished_at and what clock skew does to your answer.
Approach
- Define the interval before sorting anything: an attempt occupies [started_at, COALESCE(finished_at, lease_expires_at)). finished_at is observed and lease_expires_at is only a promise, so every attempt without a finish contributes an estimate and the whole result is a lower bound on overlap rather than an exact count.
- For peak concurrency, sweep: emit 2n endpoints, sort by (timestamp, kind) with ends ordered before starts at equal timestamps, then walk the sequence maintaining a counter per job_type and record each type's maximum with its timestamp. O(n log n) dominated by the sort, O(n) space, or O(1) extra if the sort is external and the walk streams.
- For overlap detection, do not compare attempts pairwise. A single global sort by (job_run_id, started_at) gives both the grouping and the order; within a group, keep the maximum end seen so far and report an overlap exactly when the next start is less than that running maximum, which is one linear pass after the sort.
- Half-open intervals matter and are easy to get wrong: with closed intervals an attempt ending at the same millisecond another begins reads as concurrency two, and across 50,000,000 records that artefact swamps the real signal.
Follow-up
- A handler is not idempotent and you have found 400 overlapping jobs. Which of them actually caused damage, and what would you query to find out?
- Peak concurrency for one job_type is 4 against a configured cap of 4. Is the cap working, or is the data hiding attempts that never started?
Diff a projection against the primary without per-row point reads
Hard · reconciliation · range hashing · throttling
The listing projection has drifted and some rows show a stale version. The primary holds 40,000,000 resource rows across 12,000 tenants while serving 1,200 writes and 14,000 reads per second. The obvious repair, reading each resource row and comparing its version against the projection, is correct and would eventually finish. Explain precisely why it is unacceptable here, then give a diff that finds the differing rows, state its complexity, and make it safe to run against a live primary. Replication lag is usually under 100 ms and is not bounded.
Approach
- Quantify the naive cost rather than calling it slow: 40,000,000 point reads at even 0.5 ms each is over five hours serialised, and the only lever is concurrency, which is exactly what you cannot spend. The primary's pool is sized for the write path, and 40,000,000 random reads evict the buffer cache that sustains the 85 percent cache hit rate, so the audit degrades the system it is auditing.
- Replace random access with one ordered pass per side. Both sides can be read in (tenant_id, resource_id) order, which is a sequential scan on each and a merge join in O(n) time and O(1) memory. For a dense diff that is the whole answer, and it reads the primary once instead of 40,000,000 times.
- For the expected sparse case, compare range hashes instead of rows: partition the key space, compute per range an order-independent aggregate over hash(resource_id, version), compare aggregates, and descend only into ranges that differ. With d differing rows and branching factor B, at most d ranges mismatch per level, so the drill-down examines O(d log_B(n/d)) ranges and reads full rows only in mismatching leaves.
- Aggregate with a sum modulo 2^64 or a multiset hash, never XOR. XOR is order-independent but self-cancelling, so two rows wrong in the same way, or a row duplicated on one side, leave the range aggregate matching and the range is declared clean.
Follow-up
- The diff reports 900 stale rows. How do you decide between patching those rows and rebuilding the projection from resource_revision?
- Same job, but the projection lives in a search index that cannot be scanned in key order. What changes?
Track a rolling failure rate per destination for circuit decisions
Easy · sliding window · ring buffer · circuit breaker
The egress service delivers about 1,500 webhooks per second across roughly 40,000 destinations, each call bounded by a 10 second timeout. Maintain, per destination, the failure rate over the trailing 60 seconds so a caller can ask before dispatch whether the circuit should open. Attempts arrive as (destination_id, finished_at_ms, outcome). Requirement: amortised O(1) per attempt, with total memory bounded by the destination count rather than by traffic. Give the structure, its exact memory, and the rule that stops a destination with three attempts from opening a circuit.
Approach
- Name the exact-deque version and then reject it as the default. Holding timestamps and advancing a tail pointer past anything older than now minus 60 seconds is a correct two-pointer window at amortised O(1) per attempt, but its memory tracks in-window traffic, so one destination in a retry storm holds hundreds of thousands of entries while thousands of quiet destinations hold none.
- Use a ring of 60 one-second buckets per destination, each bucket a pair of counters for attempts and failures. On an attempt, advance the ring by the elapsed whole seconds, zeroing at most min(elapsed, 60) buckets, then increment the head. That is amortised O(1) with a fixed footprint per destination.
- State the footprint: 60 buckets times two 4-byte counters is 480 bytes of payload per destination, so 40,000 destinations is roughly 20 to 25 MB with per-entry overhead, bounded by the catalogue rather than by the rate. The cost is granularity, since the oldest bucket ages out in whole seconds, which is far tighter than the decision needs.
- Require a minimum sample before the circuit may open. A destination with three attempts and three failures reads as 100 percent and is not evidence; a floor of roughly 20 attempts in the window makes the ratio meaningful, and below that floor use a run of consecutive failures as the trigger instead.
Follow-up
- The fleet is 30 instances and each sees roughly a thirtieth of a destination's traffic. Where does the rate actually live, and what does a per-instance answer get wrong?
- A destination answers in 9.5 seconds and succeeds. It is not failing but it is consuming your per-destination concurrency. What signal should open the circuit here?
Hold a per-tenant active cap against concurrent creates
Hard · write skew · isolation levels · constraints
A tenant on the standard plan may hold at most 50 resources with status='active'. The create handler runs SELECT count(*) FROM resource WHERE tenant_id = $1 AND status = 'active', compares to 50, then inserts. Two creates arrive 3 ms apart on different instances and the tenant lands at 51. Name the anomaly, say whether PostgreSQL 16 READ COMMITTED or REPEATABLE READ prevents it and why, then give an implementation that holds the cap at READ COMMITTED with the exact statements. Finally, say what changes when the cap is 'at most one running export per tenant' on job_run.
Approach
- Name it: write skew. The two transactions read an overlapping set and write disjoint rows, so there is no row-level conflict for the engine to detect and each commit is individually legal.
- Rule out the levels precisely. READ COMMITTED takes a fresh snapshot per statement and takes no lock on the counted rows, so both see 49. PostgreSQL's REPEATABLE READ is snapshot isolation: it removes non-repeatable reads and phantoms within the snapshot but still admits write skew, because the anomaly is not a re-read of a changed row, it is a read of a set that a concurrent transaction invalidates. Only SERIALIZABLE closes it, by tracking the read dependency and aborting one transaction with SQLSTATE 40001 — a guarantee that exists only if the application re-runs the whole transaction from the read.
- Convert the set predicate into a single-row conflict: keep tenant.active_resource_count and run UPDATE tenant SET active_resource_count = active_resource_count + 1 WHERE tenant_id = $1 AND active_resource_count < 50 in the same transaction as the INSERT. Zero affected rows is the cap, returned as 409. The row lock serialises the decision at any isolation level, and contention is bounded to one tenant's row — which is also the fair-scheduling unit, unlike a global counter that would convoy every tenant behind one row.
- State the cost you just took on: a counter is a second source of truth that can drift, so every path that changes status must adjust it inside the same transaction, and a periodic reconciliation has to exist, with resource_revision as the authority for what the count should have been.
Follow-up
- A resource moves from archived back to active. Which statements change, and what breaks if the counter update and the status change land in different transactions?
- The cap becomes plan-dependent and a plan can change mid-month. Where does the number 50 live, and who reads it?
Denormalise tenant onto revisions and backfill it live
Hard · denormalisation · online migration · backfill
resource_revision (revision_id, resource_id, version, actor_user_id, change_kind, patch, request_id, created_at) has 400M rows and no tenant column; tenant_id lives only on resource. Two reads need it: a tenant-scoped audit feed ordered by created_at DESC, and an offboarding purge. Both join back to resource today. Justify adding tenant_id to resource_revision against those two reads, name the anomaly the copy introduces and the constraint that prevents it, then give the ordered migration for a live table taking 1.2k writes/second — the lock each step takes, how the backfill is batched, and where each step stops being reversible. PostgreSQL 16.
Approach
- Justify from the access path rather than from taste. Without the column, the audit feed either scans resource_revision by created_at and discards other tenants' rows, or resolves the tenant's resource_ids first and probes with them — both proportional to the tenant's whole history rather than to one page. With (tenant_id, created_at DESC, revision_id DESC) it is a seek that stops at 50 rows, and the purge becomes a ranged delete instead of a join.
- Name the cost exactly: a second copy of a fact can disagree with the first. Make the disagreement unwritable rather than documented — add UNIQUE (resource_id, tenant_id) on resource so it can serve as a foreign-key target, then FOREIGN KEY (resource_id, tenant_id) REFERENCES resource (resource_id, tenant_id) on the revision table. A revision can then only ever carry its parent's tenant.
- Step one, expand: ALTER TABLE resource_revision ADD COLUMN tenant_id BIGINT NULL, with no default, so it is a catalogue change and no rewrite. It still needs ACCESS EXCLUSIVE for an instant, and that instant queues behind the longest open transaction on the table while every later query queues behind it — set lock_timeout to 2s and retry rather than wait.
- Step two, dual-write: deploy the writer that populates tenant_id on every new revision while reads still use the join. Reversible by redeploying the previous build, because nothing reads the column yet.
Follow-up
- The backfill is half finished and a rollback is required. What state is the table in, and what does the previous build do with a half-populated column?
- How do you verify the backfill actually finished, given rows are still being inserted while it runs?
Walk me through the most challenging part of the take-home assignment. How did you overcome it?
Medium · Take-Home Code Review & Practical Engine
Approach
- Pick a hard part that is a real engineering problem: a race on concurrent updates, a data model that had to support two access patterns, or pagination over changing data. Avoid tooling trouble such as getting Docker to run.
- Tell it in order: the constraint, the two or three options you considered, the one you chose and why, and how you checked it worked, ideally with the test that covers it.
- Have the file and function open so you can point at the code while you explain.
- Close with what is still weak in the solution and what you would change with more time. Naming the remaining risk yourself reads better than having it pointed out.
Follow-up
- Which option did you reject, and when would it have been the better choice?
- Which test proves that part works, and which edge case does it not cover?
- If the requirements changed tomorrow, which part of that solution would have to change first?
If we expected 100x the traffic on the feature you just built, what would break first?
Medium · Take-Home Code Review & Practical Engine
Approach
- Trace one request through the system and find the first shared, stateful resource. It is usually the database: missing indexes, N+1 queries, unbounded list endpoints or a small connection pool.
- Do the arithmetic. A pool of 10 connections with 50 ms queries tops out around 200 queries per second, so 100x traffic can hit that limit before CPU runs out. Whether it does depends on query latency versus compute per request, which is what the load test should confirm.
- Name the in-process state that stops you running a second instance, such as in-memory dictionaries, local file storage or scheduled jobs inside the web process.
- Give fixes in order of cost: indexes and query fixes, pagination, caching hot reads, stateless app instances behind a load balancer, moving slow work to a queue, then read replicas.
- Say how you would confirm the bottleneck before fixing it: a load test with k6 or Locust plus query timing and pool wait metrics, not guesswork.
Follow-up
- Is the 100x on reads or on writes, and how does that change your answer?
- How would you load test this locally or in staging?
- Once reads are cached, what breaks next?
How would you implement robust error handling and logging in your submission?
Medium · Take-Home Code Review & Practical Engine
Approach
- Sort errors into kinds: validation errors (400 or 422 with field-level details), not-found (404), state conflicts (409, such as a bid on a closed auction or a reused idempotency key with a different body), dependency failures (502 or 503, retried with backoff and jitter only if the call is idempotent) and bugs (500 with a generic message to the client).
- Use one error handler or middleware that maps exception types to a consistent body such as {code, message, details}, so route handlers do not each build their own error format.
- Use structured JSON logs with a request or correlation ID, the right levels, and one log line per error at the boundary instead of a log at every layer.
- Never log secrets or personal financial data. Mask account numbers and tokens, which matters even more in a transaction-heavy domain.
- For multi-step writes, wrap the work in a database transaction so a failure halfway rolls back cleanly. Add tests that assert the error responses, not just the happy path.
Follow-up
- How would you follow one request across two services in the logs?
- What would you alert on, and what would you only log?
- How do you stop a retry from applying a payment or bid twice?
Can you explain why you chose this specific data structure for this function?
Medium · Take-Home Code Review & Practical Engine
Approach
- Start from the operations the function performs and how often: lookups by key, ordered iteration, minimum or maximum, range queries, deduplication.
- Match each to a structure and its cost. A hash map gives O(1) average lookup by ID, a set gives deduplication, a heap gives O(log n) insert with O(1) peek at the best bid or offer, and a sorted structure or balanced tree handles range queries and price levels.
- Name the alternative you rejected and what it would have cost, for example a list scan at O(n) per lookup, or re-sorting on every insert.
- Cover memory, ordering ties and concurrent mutation, and say when the right answer is a database index rather than an in-memory structure.
Follow-up
- What if you needed lookup by ID and ordering by price at the same time?
- What happens when the data no longer fits in memory?
- Is this structure safe if two requests modify it at once?
Let's pair on adding a new endpoint to your take-home project right now.
Medium · Take-Home Code Review & Practical Engine
Approach
- Agree on the contract before typing: method and path, request and response shapes, status codes, authentication, and whether it lists, reads or creates.
- Follow the project's existing layering (route, service, data access) and naming, so the endpoint looks like the rest of your code.
- Write a test alongside it covering the happy path, a validation failure and a not-found case, and run the suite before you call it done.
- For a create endpoint on money-like data, accept an idempotency key so a client retry after a timeout cannot create a duplicate record. For a list endpoint, add cursor pagination from the start.
- Talk while you work: say what you are about to do, and say when you are choosing speed over completeness.
Follow-up
- What happens if the client retries after a timeout?
- How would you version this endpoint if its response shape had to change?
- How would you paginate it if the underlying table had millions of rows?
Design the backend architecture for a real-time bidding or trading feature.
Medium · System Design & Architecture
Approach
- Clarify scope first. Is it an auction (highest bid wins at close) or continuous order matching? How many active items, how many bids per second on the busiest one, and what latency do users see?
- Write path: a bid service validates the bid (auth, funds or hold, bid above current) and accepts it under concurrency control. Either use a conditional update such as UPDATE auctions SET high_bid = :bid, version = version + 1 WHERE id = :id AND version = :v AND high_bid < :bid, or send all bids for one auction to a single writer by partitioning on auction ID.
- Store every accepted bid in an append-only table or log with a sequence number, and publish accepted-bid events to a pub/sub layer. WebSocket gateways fan the events out to subscribers. A client that reconnects gets a snapshot plus every event after its last sequence number.
- Consistency: accepting bids must be strongly consistent per auction, while the price broadcast can lag slightly. Use idempotency keys on bid submission and a server-side clock for auction close.
- Edge cases: ties broken by sequence order, a bid in flight when the auction closes, one user's funds committed across several auctions, and one hot auction concentrating load on one partition.
Follow-up
- One auction gets thousands of bids per second. What do you change?
- A WebSocket gateway node dies. What do its clients see, and how do they recover?
- How do you stop a user from committing the same funds to bids in several auctions?
How would you design a system to securely store and retrieve user transaction histories at scale?
Medium · System Design & Architecture
Approach
- Data model: an append-only ledger with transaction_id, user_id, amount stored as integer minor units or DECIMAL (never float), currency, type, status and created_at. Fix a mistake by writing a reversing entry, never by updating the row.
- Read path: an index on (user_id, created_at DESC, id DESC) with keyset pagination: WHERE user_id = :u AND (created_at, id) < (:c, :i) ORDER BY created_at DESC, id DESC LIMIT n. The index directions match the ORDER BY (an all-ascending (user_id, created_at, id) index scanned backward also works), so the btree returns rows in order with no sort step and the row comparison bounds one contiguous range. OFFSET gets slower the deeper the page.
- Scale: partition by time, such as monthly, so old partitions can be archived or moved to cheaper storage. Hash-partition on user_id only when one database cannot hold the write volume. Run large exports as asynchronous jobs.
- Security: TLS in transit, encryption at rest with managed keys, field-level encryption for sensitive fields, and an authorization check that the requested user_id matches the caller to prevent IDOR. Add least-privilege database roles (the app role gets INSERT and SELECT on the ledger, not UPDATE or DELETE) and an audit log of who read what.
Follow-up
- A user requests a five-year statement export. How does that work without hurting live traffic?
- How do you correct a transaction that was recorded with the wrong amount?
- How would you prove to an auditor that history was never altered?
Explain how you would implement caching to improve the performance of our main product feed.
Medium · System Design & Architecture
Approach
- Ask first: is the feed the same for everyone or personalised, how fresh must prices and availability be, and what is the read-to-write ratio?
- Layer the caches. Put a short-TTL CDN or edge cache in front of an anonymous public feed. Use Redis cache-aside for the feed, storing an ordered list of item IDs per feed and cursor, plus one cache entry per item fetched with a multi-get. Then a change to one item invalidates one key, not every page.
- Invalidate on write events for items, and use a short TTL or a separate uncached lookup for fields that change fast, such as prices.
- Protect the database from stampedes with request coalescing or a short lock on rebuild, stale-while-revalidate, and jittered TTLs.
- Track hit rate and p95 latency before and after. For a personalised feed, include the user in the cache key so one user's data can never be served to another.
Follow-up
- Redis goes down. What does the feed do?
- How stale can a displayed price be, and who decides?
- How do you keep pagination stable while items are being added to the feed?
Walk me through the trade-offs between using a monolithic architecture versus microservices for our current pl
Medium · System Design & Architecture
Walk me through the trade-offs between using a monolithic architecture versus microservices for our current platform scale.
Approach
- Do not assume the platform's current scale. Ask about team size, how often deploys collide, and whether parts of the system have very different load or reliability needs.
- Monolith: one deployment, in-process calls, ACID transactions across modules, and simple debugging. The costs are coupled releases, scaling everything together and teams getting in each other's way in one codebase.
- Microservices: independent deploys and scaling, and failures stay contained in one service. The costs are network failures and latency, cross-service consistency (sagas, the outbox pattern), tracing and per-service CI, and the operational load of running something like Kubernetes.
- A defensible position: a modular monolith with enforced boundaries. Extract a service only when one component has a clearly different profile, such as a real-time price fan-out gateway, using the strangler-fig approach.
Follow-up
- Which component would you pull out first, and how would you migrate it with no downtime?
- How would you handle a write that has to update data owned by two services?
- What signals would tell you the monolith has become the bottleneck?
Listing latency scales with page size, not with filters
Easy · n+1 · latency · query batching
The tenant listing endpoint reads resource filtered by tenant_id and status, ordered by updated_at DESC, and returns each row plus the owner's display name from app_user and the actor of that resource's latest resource_revision. p99 is 55 ms at 10 rows per page and 1.4 s at 200. Database telemetry shows 401 statements per request, each under 1 ms, and nothing in the slow-query log. Diagnose the cause and give the fix, stating the statement count per request and the p99 you expect afterwards.
Approach
- Read the counters before forming a theory. 401 statements for 200 rows is one driver query plus two per row, and sub-millisecond execution with an empty slow-query log rules out a bad plan. The time is round trips, which is why it is invisible in every per-query metric and scales with rows returned rather than with filter selectivity.
- Name the two per-row statements from their normalised text: a single-row app_user lookup by user_id, and a resource_revision lookup by resource_id ordered by version DESC LIMIT 1. Confirm by dropping those two response fields and watching the statement count fall to one. That locates the calls in the serialisation layer, not the repository.
- Check that the arithmetic accounts for the whole gap. Measure one round trip to the replica in isolation; 400 trips at roughly 3 ms of network plus 0.2 ms of execution is about 1.3 s on top of a 55 ms baseline, which matches. If the multiplication had fallen short, the N+1 would only be part of the story and you would keep looking.
- Batch both lookups. Collect owner_user_ids and resource_ids from the driver query, then issue WHERE tenant_id = $1 AND user_id = ANY($2) for the users, and PostgreSQL's SELECT DISTINCT ON (resource_id) ... WHERE resource_id = ANY($2) ORDER BY resource_id, version DESC for the latest revision, which the UNIQUE (resource_id, version) index serves directly. On an engine without DISTINCT ON, use a lateral join or a row_number window. Three statements per request at any page size.
Follow-up
- The page size is capped at 200 today. What breaks first if it is raised to 2,000, and is it still this bug?
- How do you stop the next N+1 from reaching production, given that no individual query is slow and the endpoint's tests pass?
Suggested practice checklist
Each day prepares one stage of the reported loop. Recruiter and motivation work comes first, then building and defending a practice take-home, then the reported design questions grouped by technique, and finally the behavioral set.
Day 1 — Research the domain and prepare the recruiter call
- Research ALT Sales's product, users and the asset or marketplace domain the reported questions point to, and write down three specific observations.
- Write your answer to why ALT Sales and a short background summary that leads with one shipped backend feature and its result.
- Outline a cross-team story about work with an operations or sales partner, with one business number in it.
- List your recruiter questions: team stack (Python, Node.js, Go or Ruby), take-home format and final-loop participants.
Deliverable: A one-page research sheet, a written motivation answer and background summary, one cross-team story outline and a list of recruiter questions.
Day 2 — Build a practice take-home
- Build a small REST API in your stack with a relational database, for example an item listing with bids or a user transaction list.
- Add a central error handler with separate 400, 404 and 409 cases, structured logs with request IDs and a consistent error body.
- Write unit tests for the happy path, validation errors and not-found cases, plus a README with setup, assumptions and trade-offs.
- Solve the rolling failure-rate practice problem to rehearse error-rate thinking in code.
Deliverable: A runnable repository with error handling, logging, tests and a README, plus a solved failure-rate exercise.
Day 3 — Defend and extend the take-home
- Annotate your repository: three decisions with rejected alternatives, and one function you would rewrite.
- Answer out loud the hardest-part, data-structure and 100x-traffic questions against your own code, including a load test that shows whether the connection pool or CPU saturates first.
- Add a new endpoint to the project while narrating, with a test, and time how long it takes.
- Work the listing-latency debugging practice problem to rehearse finding a database bottleneck.
Deliverable: An annotated repository, recorded answers to three review questions, a new tested endpoint and a written diagnosis of the latency problem.
Day 4 — Design the bidding or trading backend
- Design the real-time bidding or trading backend: scope questions, bid write path with concurrency control, append-only bid log, pub/sub and WebSocket fan-out.
- Write the conditional UPDATE that accepts a bid only if it beats the current price and the version matches.
- Solve the per-tenant active-cap practice problem to rehearse holding a limit under concurrent creates.
- List four edge cases (ties, bids at close, funds across auctions, a hot auction) with your handling of each.
Deliverable: A diagram of the bidding system, the conditional-update SQL, a solved concurrency exercise and an edge-case table.
Day 5 — Transaction storage and zero-downtime migrations
- Design the transaction-history system: ledger schema, (user_id, created_at DESC, id DESC) index, keyset pagination query and partitioning plan.
- Write the security section: encryption, IDOR check, least-privilege roles with no UPDATE or DELETE on the ledger, and audit logging.
- Plan an expand/contract migration that adds a NOT NULL column with a batched backfill to a mutable table such as user accounts. Then write how you would add a column to the append-only ledger without rewriting rows: a constant default or a side table.
- Solve the denormalise-and-backfill-live practice problem.
Deliverable: A transaction-history design with schema and queries, a security checklist, a step-by-step migration plan and a solved backfill exercise.
Day 6 — Feed caching and architecture trade-offs
- Design caching for a product feed: ID list per feed plus per-item keys, invalidation events, stampede protection and a fallback when Redis is down.
- Prepare the monolith-versus-microservices answer, including which component you would extract first and how.
- Solve the projection-diff practice problem to rehearse checking a derived copy against its source.
- Run one full mock design round on any of the three reported design scenarios, asking scale questions first.
Deliverable: A caching design, a written architecture trade-off answer, a solved projection-diff exercise and notes from one mock design round.
Day 7 — Behavioral stories for the leadership conversations
- Write STAR outlines for a technical disagreement (ideally on architecture or concurrency control), critical feedback from a code review and turning a project around, each with a measured outcome.
- Add a pushback-on-a-requirement story and refine the cross-team story from day 1 with business impact.
- Answer all behavioral prompts out loud, then cut any sentence that does not show your decision or its result.
- Write three questions for the Head of Engineering and Head of Operations about the roadmap and the team.
Deliverable: Five STAR outlines, a recorded run-through of the behavioral prompts and a list of questions for leadership.
Behavioural preparation
Candidates report that the behavioral questions often come from the Head of Engineering or the Head of Operations, and that the final loop includes conversations with both. For a Software Engineer here, prepare stories that show you working with non-engineering teams, settling technical disagreements with evidence, and owning results in business terms. The reported tip for the operations conversation is to lead with impact.
- Tell me about a time you had to push back on a product requirement because of technical constraints. How did you handle the conversation?
How do you handle database migrations with zero downtime in a highly available system?
Medium · System Design & Architecture
Approach
- Use expand and contract. Add the new column or table, deploy code that writes to both old and new, backfill in throttled batches, switch reads, stop writing the old shape, and drop it in a later release.
- Old and new application versions run side by side during a rolling deploy, so every schema state must work with both. Never rename or drop in one step.
- In PostgreSQL, for example: build indexes with CREATE INDEX CONCURRENTLY, add a NOT NULL rule as a CHECK constraint NOT VALID followed by VALIDATE CONSTRAINT, and set lock_timeout so a migration waiting on a lock does not hold up every query behind it.
- On an append-only table such as a transaction ledger, avoid rewriting history: add the column with a constant default (a metadata-only change in recent PostgreSQL versions) or put derived values in a side table keyed by transaction ID.
- Keep a rollback plan for every step before the contract step. After contract, the way back is forward-fixing, so delay the drop until the new path has proven itself.
Follow-up
- How would you rename a column on a busy table?
- How do you backfill hundreds of millions of rows without hurting latency?
- What do you do if the backfill shows rows that break the new constraint?
Why are you interested in joining ALT Sales specifically?
Medium · Behavioral & Values Alignment
Approach
- Ground the answer in research: what ALT Sales builds, who uses it, and the asset or marketplace domain that the reported design questions and role requirements point to.
- Connect it to your own record. Name a system you built that matches the problems here, such as transactions, real-time updates or marketplace mechanics, and say what you would bring.
- Mention the practical interview format only if it truly matters to you, and back it with how you like to work.
- Avoid generic praise of growth or culture, and leave pay and perks out of this answer. End with a specific question about the product or the engineering roadmap.
Follow-up
- What would you change about the product if you joined?
- How do you think the company makes money?
- What else are you considering, and why this over those?
Tell me about a time you disagreed with a technical decision made by your team. How did you resolve it?
Medium · Behavioral & Values Alignment
Approach
- Pick a disagreement close to this loop's design topics: monolith versus services, optimistic versus pessimistic locking on a contested record, or eventual versus strong consistency for a balance or price. The same reasoning then carries into the design round.
- Use STAR, with most of the time on the reasoning. State the decision, your position, and the strongest version of the other side's case.
- Show the evidence you used to settle it, such as a benchmark, a prototype, incident history or a cost estimate, rather than arguing louder.
- Say who had final authority and how the decision was made. If you lost, show that you committed to the decision and helped it succeed.
- Give the outcome in terms a non-engineer can follow, such as incidents avoided or delivery time saved, since candidates report leadership from operations in the final loop.
Follow-up
- What would have changed your mind?
- How was your working relationship with the other person afterwards?
- Tell me about a time you were overruled and the decision turned out fine.
Describe a project where you had to collaborate closely with a non-engineering team (like Operations or Sales)
Medium · Behavioral & Values Alignment
Describe a project where you had to collaborate closely with a non-engineering team (like Operations or Sales).
Approach
- Pick a project with an operations, sales or other non-engineering partner where you shaped the requirement, not one where you only received a ticket.
- Show how you learned their workflow, for example by sitting with the team or mapping their manual steps, and how you explained technical trade-offs in their terms.
- Give a business result: hours of manual work removed, errors cut, faster turnaround or revenue affected. Candidates report that the operations conversation focuses on business impact.
- Include one point of tension, such as a request you could not build as asked, and how you agreed on an alternative.
Follow-up
- What did they ask for that you said no to, and how did you say it?
- How did you measure whether the project worked for them?
- What did you learn about their work that changed your design?
What is a piece of critical feedback you received recently, and how did you act on it?
Medium · Behavioral & Values Alignment
Approach
- Choose recent, specific feedback about your work. Code review feedback fits this loop well, since candidates report a review of your take-home code in the final round: for example, missing tests on error paths or functions that mixed data access with business logic. Avoid a disguised strength.
- Say who gave it and your first reaction honestly, then the concrete change you made.
- Show evidence that the change lasted, such as a later review, a metric or repeat feedback that changed.
- Link it to how you prepared your take-home if it applies, for example writing your own tests for bad input because of that feedback.
Follow-up
- Tell me about feedback you disagreed with.
- How do you ask for feedback now?
- Tell me about hard feedback you gave someone else.
Tell me about a time you took ownership of a failing project and turned it around.
Medium · Behavioral & Values Alignment
Approach
- Open with the signals that the project was failing: missed milestones, a growing defect count, stakeholders losing trust. Then say exactly what you took ownership of.
- Show your diagnosis before your actions: root causes such as unclear scope, a blocking dependency or an unrealistic estimate.
- Describe the actions: re-scoping with stakeholders, cutting or sequencing features, unblocking people and resetting communication with a regular status update.
- End with a measurable outcome an operations leader would track, such as a launch date recovered, manual work removed or errors cut, since candidates report the operations conversation focuses on business impact. Do not blame the previous owner, and do not tell a turnaround that someone else drove.
Follow-up
- What did you cut, and who had to agree to it?
- Looking back, when should someone have noticed it was failing?
- What would you have done if re-scoping was not allowed?