cswork / libraryOpen practice workspace

Interview guides · Anakin (Yc S21)

Anakin (Yc S21) Software Engineer Interview Guide

Anakin (Yc S21) Software Engineer interview guide: DSA, machine coding, high-level design and SQL questions with worked approaches and a 7-day plan.

The 60-second brief

Anakin carries the Yc S21 tag in its name. Candidate accounts do not describe its product in any detail, so this guide makes no claims about it. What they do describe is the Software Engineer job, and it is backend work. The role is described as designing, building and deploying backend services, defining database schemas and REST APIs, and writing code that is safe under concurrent threads. It involves working with product managers on technical requirements and with operations teams on deployments, and taking part in code reviews. The role description covers owning a feature through to production. The reported questions mix algorithm problems, an object-oriented build exercise, distributed design, and questions on SQL and networking.

This guide is for engineers interviewing for the Software Engineer role at Anakin (Yc S21), especially those coming from backend work. The requirements candidates describe are deep fluency in one object-oriented language (C++, Java and Python are named), solid data structures, algorithms and OOP, and hands-on SQL, relational schema design and REST APIs. The listed requirements add distributed systems and fault tolerance for mid-level and senior candidates. Listed nice-to-haves are AWS or GCP, Docker or Kubernetes, and caching. The guide covers each reported stage, guidance for every reported question, the mistakes that cost points on these question types, and a seven-day plan.

Candidates report six stages over roughly four to six weeks: a pre-screening assessment, technical deep dives, a data structures and algorithms round, a machine coding round, a high-level design round and a culture fit discussion. Some accounts describe three to five rounds over two to four weeks, and the count can shift with seniority.

Focus areas

  • Data Structures & Algorithms (DSA)
  • SQL Queries
  • Live Coding

Overview

The twenty questions candidates report fall into four groups, and each needs a different kind of practice. The algorithm problems cover linked lists (multiplying two lists, reversing in groups of k), dynamic programming (a weighted grid path, longest increasing subsequence) and graphs (cycle detection in a directed graph). The machine coding problems ask you to build a small object-oriented system: a thread-safe cache, a parking lot, a rate limiter, a library system, and a refactor of tightly coupled code. The design questions cover a chat backend, a URL shortener, consistency across microservices, a checkout API and schema, and fault tolerance for payments. The fundamentals cover SQL writing and tuning, normalization, TCP versus UDP, and DNS resolution. Solving algorithm problems alone does not prepare you for this mix.

For the algorithm stage, candidates name dynamic programming and graphs as the most common topics, followed by linked lists and trees, and the step from a brute-force solution to an optimal one. Tries, segment trees, Dijkstra and A* come up less often. The coding stages are reported as language-agnostic, with C++, Java and Python named. Practise writing code that compiles on the first run, in the language you use every day, and state the time and space complexity before the interviewer asks.

The machine coding round is a different skill. Candidates describe building a working mini-application under a time limit and sometimes running it locally while sharing their screen. They also report questions on OOP principles, schema design, the Strategy, Factory and Singleton patterns, and occasionally thread safety. Example builds besides the reported questions include a thread-safe logging framework and an inventory system. Rehearse one full build from start to finish, with interfaces and a demo you can run, before the real round.

Three of the five design questions involve money or state that must not be lost: checkout, payments and cross-service consistency. Build one toolkit and reuse it across them: idempotency keys, a persisted state machine, the transactional outbox, sagas with compensating steps, and reconciliation. Candidates report that high-level design is emphasised heavily for mid-level and senior hires, and that it covers API design, SQL versus NoSQL, indexing, caching with Redis or Memcached, queues such as Kafka or RabbitMQ, and microservices. The final stage covers culture fit and a deep dive into your resume, so prepare project stories with concrete numbers.

The Anakin (Yc S21) interview process

The six stages below follow the order candidates report. For each stage you get what candidates describe, what the format covers, and which of this guide's question types to rehearse for it.

Pre-screening Assessment

Candidates describe the first stage as an initial assessment or a recruiter call to align on your background and the role's expectations. What the assessment itself contains is not reported, so ask the recruiter which format you will get and what it covers. If it is a call, give a tight account of your backend experience that matches the requirements candidates list: one object-oriented language, SQL and relational design, REST APIs, and for experienced hires, distributed systems.

What to demonstrate

  • Whether your background lines up with the listed requirements: an OOP language, DSA, SQL and REST APIs
  • How clearly you can summarise your experience against the role

How to prepare

  • Write a 90-second summary of one backend system you built: the schema, the API, the scale and the hardest bug
  • Confirm with the recruiter whether this stage is a call or an assessment, what it covers, and which language you can use
  • Keep a few core coding problems warm in case the assessment includes coding

Technical Deep Dives

Candidates describe a series of technical interviews that test coding speed, architectural thinking and practical engineering. Accounts do not split these sessions further, so prepare for coding and for discussing your own systems. One reported design prompt asks you to walk through the architecture of your most complex recent project and the bottlenecks you overcame. Candidates also report the SQL and networking fundamentals questions without saying which stage they come from, so have those ready before this stage starts. Candidates also report that requirements can be vague or change mid-session. Ask about input sizes and latency targets before you choose an approach.

What to demonstrate

  • Speed and correctness on practical coding problems
  • How you explain the architecture of systems you have built, and the trade-offs behind them
  • Command of backend fundamentals: SQL, databases and networking

How to prepare

  • Pick two past projects and, for each, write down the schema, the main API calls, the bottleneck you hit and how you measured the fix
  • Rehearse the fundamentals questions aloud: TCP versus UDP, DNS resolution step by step, normalization and its trade-offs
  • Write the purchases query and outline how you would tune a slow multi-join query using EXPLAIN ANALYZE

Data Structures and Algorithms

Candidates describe this as a live coding round on screen that tests problem-solving speed and accuracy. You can use the language you know best, and C++, Java and Python are named. The reported topics are dynamic programming and graphs most often, then linked lists and trees, and improving a brute-force solution to the optimal complexity. Tries, segment trees, Dijkstra and A* come up less often. The reported algorithm questions are the ones to rehearse for this format: linked-list multiplication, the weighted grid path, directed cycle detection, longest increasing subsequence and reversing in groups of k. For each one, state the brute-force solution, give the optimal complexity, write the code, then trace it on an edge case.

What to demonstrate

  • Picking the right data structure and algorithm for the problem
  • Working, syntactically correct code in your chosen language
  • Getting from a brute-force solution to optimal time and space complexity, and explaining why

How to prepare

  • Write longest increasing subsequence in both O(n^2) and O(n log n), with the subsequence reconstructed
  • Write directed cycle detection with three-colour DFS and with Kahn's algorithm, then explain when each fits
  • Write the grid path DP with a rolling array, then explain why four-direction movement needs Dijkstra
  • Do one tree traversal problem, since trees are a reported common topic, and one Dijkstra or trie problem, since those are reported as less common

Machine Coding

Candidates describe a round that mimics daily work: you get requirements and build a small, well-structured, working application under a time limit. You may have to share your screen and run the code locally, so have your IDE, compiler and dependencies set up and tested beforehand. Reported focus areas are OOP (encapsulation, inheritance, polymorphism), schema and data modelling, the Strategy, Factory and Singleton patterns, and less often thread safety and testable interfaces. The reported machine coding questions show the range: a thread-safe cache, a parking lot, an OOP rate limiter, a library system and a refactor. Work in order: entities, then interfaces, then core flows, then a runnable demo. Mention extension points as you go.

What to demonstrate

  • Modular, extensible OOP code that runs
  • Sound class and schema modelling of real-world entities
  • Appropriate use of design patterns and, where needed, thread safety

How to prepare

  • Build the parking lot from start to finish in your own IDE with a runnable main and two tests, and time the whole build
  • Build the token-bucket rate limiter behind a RateLimiter interface with an injectable clock
  • Write characterization tests for a small tangled class, then refactor it with Strategy and constructor injection

High-Level Design

Candidates describe a round on designing scalable distributed systems for high throughput. Candidates report that design is emphasised heavily for mid-level and senior hires. The reported topics are API design (REST or GraphQL), scalability and fault tolerance, SQL versus NoSQL, query optimisation and indexing, and less often caching with Redis or Memcached, queues such as Kafka or RabbitMQ, and microservices. Example prompts besides the reported questions include a ride-sharing backend and a distributed payment gateway. The reported design questions (chat, URL shortener, microservice consistency, checkout, payments) are the practice set for this format. For each one, settle requirements and scale, sketch the API and data model, draw the read and write paths, then go through failures and consistency trade-offs.

What to demonstrate

  • API and schema design for the stated requirements
  • Scalability and fault tolerance under heavy load
  • Reasoning about consistency versus availability, and justifying database and caching choices

How to prepare

  • Design the checkout flow end to end: endpoints, tables, the idempotency key and the order state machine
  • Compare 2PC, orchestrated saga and choreographed saga on one order-payment-inventory example, with the transactional outbox
  • Design the chat backend and say exactly what happens when a WebSocket gateway node dies

Culture Fit Discussion

Candidates describe the final stage as a culture fit discussion that also digs into your resume. Candidates report that the discussion covers ownership, resilience, communication and changing requirements. The role involves working with product and operations, so prepare to explain a technical decision to someone outside engineering. Bring stories with real stakes: a feature you owned from design to production, a scope that changed late, a production bug you fixed. Each story should show a decision you made and a measurable result.

What to demonstrate

  • End-to-end ownership of past work, shown through specific decisions
  • Clear explanation of technical choices to technical and non-technical listeners
  • How you handle shifting requirements and pressure

How to prepare

  • Write four STAR stories: full ownership, a changed requirement, a production incident, and a trade-off between speed and quality
  • For each resume project, prepare the numbers: traffic, latency, data size, team size and your share of the work
  • Prepare questions about the team's backend stack and release practices to ask at the end

Where candidates lose points

Stopping at the first working algorithm, such as the O(n^2) longest increasing subsequence or a recursive grid path with no memoization, and never stating the complexity.

Candidates report that the algorithm round is about getting from brute force to the optimal solution. For every reported DSA question, practise saying the brute-force complexity in one sentence, then the optimal one and the observation that unlocks it (binary search on tails, rolling DP row, three-colour DFS). Write that version, then trace one edge case aloud.

Machine coding output that is one large class with no interfaces, or that never runs because the IDE, compiler or dependencies were not ready.

Before the round, build the parking lot or rate limiter from start to finish in the exact environment you will share, with a runnable main. In the round, write entities and interfaces first and get a minimal flow running early, then add features through strategies. Code that runs and shows its extension points beats a larger design that does not run.

Calling a cache or rate limiter thread-safe because every method is synchronized, without noticing that an LRU get changes shared state or that a value is loaded while the lock is held.

For the cache, rate limiter and parking lot spot allocation, name the shared state, the invariant that must hold, and which lock protects it. Rehearse the move from one mutex to sharded locks, and the compute-if-absent pattern that stops duplicate loads.

Designing checkout or payments around the happy path, so a timeout or retry can charge twice or leave an order in an unknown state.

For the checkout, payment and microservice consistency questions, put idempotency keys, a persisted state machine with an UNKNOWN state, the outbox, and reconciliation into the first version of the diagram. Practise walking through the case where the provider charged the card but your service crashed.

Answering the SQL tuning question with 'add an index', or writing the purchases query with a function on the timestamp column and line-item counting.

Practise reading an EXPLAIN ANALYZE output and naming one concrete fix per symptom. For the purchases query, say your definitions out loud (rolling window, distinct orders, excluded statuses), and keep the filter column bare so an index can be used.

Questions & practice

Implement a function to perform linked list multiplication.

Medium · Data Structures and Algorithms

Approach

  1. Clarify the representation first: each list holds one digit per node, most significant digit first, and the answer is either a number modulo 1e9+7 or a new list. These two versions call for different solutions.
  2. For the modular version, walk each list once with num = (num * 10 + digit) % MOD, then return (a * b) % MOD. This runs in O(n + m) time and O(1) space, and never builds an integer that overflows.
  3. If the product must come back as a list of any length, reverse both lists (or copy them into arrays). Then do schoolbook multiplication into an array of size n + m, adding a[i] * b[j] at position i + j, and propagate carries. This is O(n * m) time and O(n + m) space.
  4. Remove leading zeros from the result, but keep a single 0. Test zero times anything, single-node lists, lists that already have leading zeros, and very long inputs.

Follow-up

  • Can you return the product as a linked list without reversing the inputs, for example by using a stack?
  • How would you handle negative numbers stored as a sign flag in the head node?
  • For very long inputs, what does Karatsuba multiplication change about the complexity?

Solve a dynamic programming problem to find the optimal path in a weighted grid.

Medium · Data Structures and Algorithms

Approach

  1. Pin down the moves before writing anything. If you can only move right or down, the grid is a DAG and DP applies: dp[i][j] = grid[i][j] + min(dp[i-1][j], dp[i][j-1]), with the first row and first column filled from a single direction.
  2. This takes O(m * n) time. Space drops from O(m * n) to O(n) with a single rolling row, because each cell needs only the cell above it and the cell to its left.
  3. To return the path itself, keep the full table (or a parent pointer per cell) and walk back from the bottom-right corner. Blocked cells get infinity and must never be selected.
  4. If moves can go in all four directions, cycles break the DP ordering. Use Dijkstra with a min-heap (O(mn log mn)) when weights are non-negative. If weights can be negative and moves go both ways, any two adjacent cells whose combined weight is negative form a negative cycle, so the shortest path is undefined. Bellman-Ford only detects that case. Forbidding revisits instead turns it into a different, NP-hard problem, so ask which version is meant.

Follow-up

  • How does the solution change if you are allowed to move up and left as well?
  • What if you want the maximum-sum path, or the number of minimum-cost paths?
  • How would you handle a variant where you may remove up to k obstacles?

Implement a graph traversal algorithm to detect cycles in a directed graph.

Medium · Data Structures and Algorithms

Approach

  1. Represent the graph as an adjacency list. Run DFS with three states per node: unvisited, in the current recursion stack, and finished. Reaching an edge to a node that is still on the stack (a back edge) means there is a cycle.
  2. Start DFS from every unvisited node, so disconnected components are checked too. This is O(V + E) time and O(V) space.
  3. Alternative: Kahn's algorithm. Compute in-degrees, repeatedly remove nodes with in-degree zero, and if fewer than V nodes get processed, the rest contain a cycle. This method also gives a topological order when there is no cycle.
  4. Handle self-loops (an immediate cycle), parallel edges, and deep graphs. On large inputs, recursive DFS can overflow the stack, so write an iterative version with an explicit stack.

Follow-up

  • How would you return the actual nodes of one cycle, not just a yes or no?
  • Why is a two-state visited set enough for undirected graphs but wrong for directed ones?
  • How does this apply to ordering build tasks or course prerequisites?

Given an array of integers, write an optimized solution to find the longest increasing subsequence.

Medium · Data Structures and Algorithms

Approach

  1. Start with the O(n^2) DP: dp[i] is the length of the longest increasing subsequence ending at i, computed as 1 + max(dp[j]) over j < i with a[j] < a[i]. The answer is max(dp).
  2. Then improve it to O(n log n) with patience sorting. Keep tails[k], the smallest possible tail of an increasing subsequence of length k + 1. For each x, binary-search the first tail >= x (lower bound) and replace it, or append x if it is larger than every tail.
  3. The tails array is not the subsequence itself. To reconstruct the subsequence, store the index placed at each length and a predecessor index for each element, then walk back from the last one.
  4. Decide strict versus non-decreasing: for non-decreasing, search for the first tail > x (upper bound). Check an empty array, all-equal values (answer 1 when strict) and strictly decreasing input.

Follow-up

  • Return the subsequence itself, not just its length.
  • How would you count the number of longest increasing subsequences?
  • How does this extend to nesting envelopes, where each item has two dimensions?

Reverse a linked list in groups of 'k' nodes.

Medium · Data Structures and Algorithms

Approach

  1. Use a dummy node before the head and a pointer prevGroupTail. For each group, first walk k nodes ahead to confirm a full group exists. If fewer than k nodes remain, leave them as they are (the usual rule; confirm it).
  2. Reverse the k nodes in place with the standard three-pointer loop. Then connect prevGroupTail to the new group head and the old group head (now the tail) to the next group's first node.
  3. This is O(n) time and O(1) extra space. A recursive version is shorter but uses O(n / k) stack frames.
  4. Test k = 1 (no change), k equal to the list length, k larger than the length, an empty list, and lengths that are and are not multiples of k.

Follow-up

  • What changes if the leftover nodes at the end must also be reversed?
  • How would you reverse only every other group of k?
  • How would you do this on a doubly linked list, and what extra pointers must be fixed?

Design a thread-safe class structure for an in-memory cache.

Medium · Machine Coding and OOP

Approach

  1. Define the interface first: get, put, delete, and optionally a TTL per entry. Then choose eviction. LRU uses a hash map plus a doubly linked list for O(1) get and put. Put the eviction policy behind an interface so LFU or FIFO can be swapped in.
  2. An LRU get changes recency order, so even reads mutate shared state. A read-write lock does not help. The simplest correct version guards the map and the list with one mutex.
  3. For throughput, shard by key hash into N segments, each with its own lock and its own LRU. The trade-off is that eviction becomes approximately LRU across the whole cache. In C++ that is a std::mutex per shard (std::shared_mutex only pays off for operations that do not reorder recency).
  4. Never hold the lock while loading a missing value. Use a per-key future (compute-if-absent) so concurrent misses on the same key trigger one load. In Java that is a ConcurrentHashMap of CompletableFuture or computeIfAbsent. In C++ store a std::shared_future in the shard's map for in-flight loads, release the shard lock before calling the loader, and remove the entry if the load fails. Expire TTL entries lazily on read, plus a background sweep. Return immutable values or copies so callers cannot mutate cached objects.

Follow-up

  • How do you prevent a stampede when a popular key expires?
  • How would you measure whether sharding actually reduced lock contention?
  • What changes if the cache has to be shared across several service instances?

Write a modular program to simulate a parking lot system, ensuring your code is easily extendable.

Medium · Machine Coding and OOP

Approach

  1. Ask about requirements first: number of floors, spot sizes (motorcycle, compact, large, EV), entry and exit gates, the pricing model, and whether reservations matter. Then name the entities: ParkingLot, Floor, ParkingSpot, Vehicle subtypes, Ticket, Gate and Payment.
  2. Put the parts likely to change behind interfaces. A SpotAllocationStrategy decides how a spot is chosen (nearest first, by floor), and a PricingStrategy decides the fee (hourly, flat, weekend). A new vehicle type or fee rule then needs a new class, not edits to existing ones.
  3. Keep a set or queue of free spots per spot type on each floor. Allocation and availability counts then run in O(1), with no scan over all spots.
  4. Two gates can grab the same spot, so make allocation atomic: either lock per floor or mark spots with compare-and-set. Finish with a runnable main that parks, unparks and prints the fee, plus a test for the lot-full case.

Follow-up

  • Add EV spots with charging fees: which classes change?
  • How would you show live free-spot counts per floor on a display board?
  • How would you persist state so a restart does not lose active tickets?

Design the data models and core logic for a library management system.

Medium · Machine Coding and OOP

Approach

  1. Separate the catalogue entry (Book: ISBN, title, authors) from the physical copy (BookCopy: barcode, status). Model Member, Loan (copy, member, issued_at, due_at, returned_at), Reservation and Fine as their own entities.
  2. Schema: books(isbn PK), copies(id PK, isbn FK, status), members(id, max_loans), loans(id, copy_id FK, member_id FK, issued_at, due_at, returned_at). A unique partial index on loans(copy_id) WHERE returned_at IS NULL makes it impossible to check out the same copy twice.
  3. For checkout, check the member's loan limit and unpaid fines, find an available copy, then create the loan and update the copy status in one transaction. For return, close the loan, compute the fine through a FinePolicy strategy, and hand the copy to the first reservation in the FIFO queue.
  4. Index for search on title and author. Keep notifications (due reminders, holds ready) behind an interface so email and SMS can be added later.

Follow-up

  • Two members try to check out the last copy at the same moment: what stops a double checkout?
  • How would you change the fine policy for students without touching the checkout code?
  • How would you keep borrowing history if members can delete their accounts?

Refactor a given block of tightly coupled code using appropriate design patterns.

Medium · Machine Coding and OOP

Approach

  1. Before changing anything, write characterization tests that pin down the current behaviour, so every later step can be checked against them.
  2. Name the specific smells. A switch on a type field becomes Strategy or polymorphism. Dependencies created with new inside methods become constructor injection behind interfaces. Scattered construction logic becomes a Factory. Hidden global state becomes an explicit dependency.
  3. Refactor in small steps that keep the tests green: extract method, then extract class along responsibilities, then introduce the interfaces. Show the result with the tests still passing.
  4. Justify every pattern by the change it makes cheaper, and say where you would leave the code alone. Adding patterns the code does not need is a mistake in itself.

Follow-up

  • How do you ship a large refactor to production safely in small steps?
  • Which part of this code would you leave coupled, and why?
  • How do the new interfaces make the code easier to unit test?

Write an SQL query to find all users who have made more than 3 purchases in the last 30 days.

Medium · Core Fundamentals (SQL & Networks)

Approach

  1. Assume a table purchases(purchase_id, user_id, purchased_at, status) and state that assumption. Query: SELECT user_id, COUNT() AS n FROM purchases WHERE purchased_at >= CURRENT_TIMESTAMP - INTERVAL '30 days' GROUP BY user_id HAVING COUNT() > 3.
  2. Clarify the definitions. More than 3 means at least 4. If rows are line items, count orders with COUNT(DISTINCT order_id). Decide whether refunded or cancelled purchases count, and whether the window is a rolling 30 days or calendar days in a given time zone.
  3. If you need user names, join users after aggregating. Purchases to users is many-to-one, so joining first would not multiply rows, but aggregating first keeps the GROUP BY on user_id alone and joins only the qualifying users. Save the row-multiplication warning for one-to-many joins, such as joining to order_items before counting. Keep the filter column bare: wrapping it as DATE(purchased_at) stops the database from using an index on it.
  4. An index on (purchased_at, user_id), or on (user_id, purchased_at) for per-user lookups, supports this. Say which one fits the table's other queries.

Follow-up

  • Find users who made more than 3 purchases in any 30-day window, not only the last 30 days.
  • Return each qualifying user's total spend in the window as well.
  • How would you also list users with zero purchases in the window?

How do you optimize a slow-running SQL query with multiple joins?

Medium · Core Fundamentals (SQL & Networks)

Approach

  1. Measure before changing anything: run EXPLAIN ANALYZE. Look for sequential scans on large tables, estimated row counts far from actual counts (stale statistics, so run ANALYZE), nested loops over a large outer input, and sorts or hashes that spill to disk.
  2. Index the join keys and filter columns with composite indexes in the right column order. A covering index lets the database answer from the index alone. Then check that predicates can use the index: no functions on indexed columns, no leading-wildcard LIKE, no OR across columns that defeats an index.
  3. Shrink rows early. Filter before joining, aggregate in a subquery before joining to a large table, select only the columns you need, and replace a DISTINCT that only hides one-to-many fan-out with EXISTS or a corrected join.
  4. If the query is still slow, consider keyset pagination, a materialized view or a denormalized read table for hot paths. Re-measure after each change and note the extra write cost of every new index.

Follow-up

  • The planner ignores the index you added: what are the likely reasons?
  • How much write overhead do extra indexes add, and how would you decide which to keep?
  • When is denormalizing the right answer here?

Describe the concept of database normalization and its trade-offs.

Medium · Core Fundamentals (SQL & Networks)

Approach

  1. Define the normal forms with one running example, such as an orders table that repeats customer name and address on every row. 1NF requires atomic values with no repeating groups. 2NF removes columns that depend on only part of a composite key. 3NF removes columns that depend on another non-key column. BCNF requires every determinant to be a candidate key.
  2. Benefits: no update, insert or delete anomalies, less redundant storage, and each fact stored in one place. Costs: reads need more joins, and joins across large tables add latency.
  3. Denormalize deliberately on read-heavy paths: stored counters, a summary table or materialized view, or caching. Name the mechanism that keeps the copy correct, whether triggers, the same transaction or asynchronous rebuilds.
  4. Separate denormalization from storing a historical fact. Copying the unit price into an order line is correct modelling, because the price at purchase time is a separate fact from the current price.

Follow-up

  • Where would you denormalize in the checkout schema, and how would you keep it consistent?
  • How does this thinking change when the data lives in a document store?
  • Show a table that is in 3NF but not in BCNF.

Implement a basic rate limiter using Object-Oriented principles.

Medium · Machine Coding and OOP

Approach

  1. Define a RateLimiter interface with allow(clientKey). Behind it, write one or two implementations and state their trade-offs. A fixed window is simple but can let through twice the limit around a window boundary. A sliding-window log is exact but stores every timestamp. A sliding-window counter approximates the log. A token bucket allows bursts up to its capacity while holding a steady average rate.
  2. Implement the token bucket with a lazy refill: tokens = min(capacity, tokens + elapsed * rate), then take one token if available. Each call is O(1) and needs no background timer.
  3. Keep buckets per client in a concurrent map and synchronize per bucket, so one client's traffic does not block others. Inject a Clock so tests can move time forward deterministically.
  4. Read limits from configuration per tier or endpoint, with a factory that picks the algorithm. Evict buckets that have been idle for a while, or the map grows without bound.

Follow-up

  • How would this work across several application servers sharing one limit, for example with Redis and an atomic script?
  • What response and headers should a rejected request get?
  • How would you give premium clients a different limit without changing the limiter classes?

Design a scalable backend architecture for a real-time chat application.

Medium · System Design and Architecture

Approach

  1. Settle scope first: one-to-one and group chat, online presence, delivery and read receipts, history, and offline push notifications. Estimate concurrent connections and messages per second.
  2. Clients hold WebSocket connections to stateful gateway servers. A registry (for example Redis) maps each user to their gateway. A message service persists each message, then routes it to the recipient's gateway through pub/sub or a queue partitioned by conversation_id, which keeps order within a conversation.
  3. Store messages keyed by (conversation_id, message_id) in a wide-column store or sharded SQL, with time-sortable IDs. Clients generate a message ID so retries are deduplicated, and the server acknowledges receipt.
  4. For offline recipients, store the message and send a push notification. On reconnect, the client syncs from its last-seen message ID. For large groups, avoid writing a copy to every member and let members read from the conversation instead.

Follow-up

  • A gateway node dies with 50,000 open connections: what happens and how do clients recover?
  • How do you keep message order consistent across one user's several devices?
  • How would presence scale without flooding every contact with updates?

How would you design a distributed URL shortener service?

Medium · System Design and Architecture

Approach

  1. API: POST /urls with long_url and an optional custom alias and expiry, returning a short code. GET /{code} redirects. Use 301 if browsers may cache the redirect, and 302 if you need every click for analytics.
  2. Key generation: base62-encode a counter, with each server leasing ID ranges from a coordinator so there are no collisions. A random code needs a collision check, and a pre-generated key pool is a third option. Seven base62 characters give about 3.5 trillion codes.
  3. The service is read-heavy, so put a cache (Redis) and a CDN in front of a key-value store keyed by code. Writes are rare and simple, so partition the store by code.
  4. Collect analytics asynchronously through a queue, not on the redirect path. Add rate limits and malicious-URL checks on creation, and lazy expiry plus a cleanup job for expired links.

Follow-up

  • How do you handle two users requesting the same custom alias?
  • Should the same long URL always return the same short code? What does that cost?
  • How would you count clicks per link at high volume without slowing redirects?

Discuss how you would ensure data consistency across multiple microservices in a failure scenario.

Medium · System Design and Architecture

Approach

  1. Anchor the answer on a concrete flow, such as an order that reserves inventory and charges payment. Then explain why one ACID transaction cannot span the separate services' databases.
  2. Compare the options. Two-phase commit gives atomicity, but locks are held and the coordinator can block. A saga runs local transactions with a compensating action for each (orchestrated by a coordinator or choreographed through events) and accepts eventual consistency.
  3. Avoid dual writes with the transactional outbox: the service writes its state change and the event to an outbox table in one transaction, and a relay publishes the event. Delivery is at-least-once, so consumers must be idempotent, deduplicating on event ID.
  4. Add retries with backoff, a dead-letter queue, timeouts that trigger compensation, user-visible pending states, and a reconciliation job that finds and repairs drift.

Follow-up

  • What do you do when a compensating action itself fails?
  • How do you handle events that arrive out of order?
  • When would you accept two-phase commit despite its costs?

Design the API and database schema for an e-commerce checkout flow.

Medium · System Design and Architecture

Approach

  1. Endpoints: add and update cart items, then POST /checkout with an Idempotency-Key header. Checkout creates an order in PENDING_PAYMENT and reserves stock. A payment provider confirms through a webhook, which moves the order to CONFIRMED (or FAILED, releasing the stock).
  2. Tables: products, inventory(product_id, available, reserved), carts, cart_items, orders(id, user_id, status, total_minor, currency, idempotency_key UNIQUE), order_items(order_id, product_id, qty, unit_price_minor), and payments(order_id, provider_ref, status, amount_minor).
  3. Store money as integer minor units and copy the unit price into order_items, so later price changes do not rewrite past orders. Reserve stock with one conditional update: SET available = available - q, reserved = reserved + q WHERE product_id = ? AND available >= q. Confirm moves the units out of reserved (reserved = reserved - q), and release moves them back (reserved = reserved - q, available = available + q).
  4. Define the error responses before the success path. Status codes are a convention to agree with the API's consumers. The IETF Idempotency-Key specification uses 422 for a reused key with a different payload and 409 for a request with the same key still in flight, and you can use 409 for out of stock too. Add a clear payment-failed state, and release reservations that expire.

Follow-up

  • The payment succeeded but your order update failed: how is that state repaired?
  • How would checkout hold up during a flash sale on one product?
  • Where do coupons and tax calculation fit into this schema?

Explain how you would implement fault tolerance in a highly available payment processing system.

Medium · System Design and Architecture

Approach

  1. Persist a payment record and its state machine (CREATED, AUTHORIZING, AUTHORIZED, CAPTURED, FAILED, UNKNOWN) before calling the provider, and pass an idempotency key the provider honours. A retry then cannot charge twice.
  2. Treat a timeout as UNKNOWN, not as failed. Resolve it by querying the provider with your reference, through a background job. Retry with exponential backoff and jitter only on operations that are idempotent.
  3. Wrap provider calls in timeouts and a circuit breaker, with a fallback provider where routing allows. Run the database across availability zones with synchronous replication for the ledger, and keep the ledger as append-only double-entry records.
  4. Publish events through an outbox, deduplicate provider webhooks, and reconcile daily against provider settlement files to catch anything the online path missed.

Follow-up

  • The provider charged the card but your service crashed before recording it: walk through the recovery.
  • A user double-clicks Pay: what stops a second charge?
  • What changes if a whole region fails over in the middle of a transaction?

Explain the difference between TCP and UDP, and when you would use each.

Medium · Core Fundamentals (SQL & Networks)

Approach

  1. TCP is connection-oriented: a three-way handshake, then a reliable, ordered byte stream with acknowledgements, retransmission, flow control through the receive window, and congestion control. The costs are handshake latency and head-of-line blocking when a segment is lost.
  2. UDP is connectionless: independent datagrams with an 8-byte header and no ordering, retransmission or congestion control. Message boundaries are preserved, and the application has to handle any loss it cares about.
  3. Use TCP where every byte must arrive in order: HTTP/1.1 and HTTP/2, database connections, file transfer, most APIs. Use UDP where late data is worthless or requests are tiny: DNS queries, voice and video calls, game state, metrics.
  4. Mention QUIC, the transport under HTTP/3. It builds reliability and TLS on top of UDP to get faster connection setup and to avoid TCP's head-of-line blocking across streams.

Follow-up

  • What happens to a video call over UDP compared with a file download over TCP when 2% of packets are lost?
  • Why does DNS sometimes switch to TCP?
  • What is TIME_WAIT, and why can it exhaust ports on a busy client?

Explain how DNS resolution works step-by-step.

Medium · Core Fundamentals (SQL & Networks)

Approach

  1. Resolution checks caches first: the browser cache, then the operating system cache and hosts file. On a miss, the OS stub resolver sends a recursive query to a configured resolver, such as the ISP's or a public one.
  2. On a miss, the resolver asks a root server, which refers it to the TLD servers (for example .com). The TLD servers refer it to the domain's authoritative name servers, using glue records where needed. The authoritative server returns the A or AAAA record, possibly after following a CNAME chain.
  3. The resolver caches every answer for its TTL, including negative answers, and returns the address. Queries normally travel over UDP port 53, with TCP for large responses and zone transfers.
  4. Then connect it to the request: the TCP and TLS handshakes to that IP, then the HTTP request. Name the other record types you would expect: NS, MX, TXT and CNAME.

Follow-up

  • Why does a DNS change seem to take hours to reach everyone?
  • How can DNS be used for load balancing or geographic routing, and what are its limits?
  • What do DNSSEC and DNS over HTTPS each protect against?

p99 jumped on one listing filter while p50 stayed flat

Medium · query planning · composite index · percentiles

After a release that added an owner_user_id filter to the resource listing, p99 rose from 90 ms to 1.9 s while p50 stayed at 40 ms. Traffic and row counts are unchanged. resource carries the index (tenant_id, status, updated_at DESC, resource_id DESC). The new query filters tenant_id and owner_user_id, orders by updated_at DESC, resource_id DESC, and takes 20 rows. On PostgreSQL, explain the shape of the regression, prove it from a query plan, and give the index you would add.

Approach

  1. Start from the shape. A flat p50 with a moved p99 means a subset of requests changed cost, not all of them, so the first job is naming the subset. Bucket the endpoint's latency by the tenant's row count; the natural hypothesis is that large tenants are a small share of requests and all of the tail.
  2. Get the plan for the new query on a large tenant with EXPLAIN (ANALYZE, BUFFERS). Expect an index scan over the tenant's range, a filter discarding most of it, then a Sort feeding the Limit, possibly reporting Sort Method: external merge Disk. Read actual rows on the scan node, not estimated.
  3. Explain why the existing index cannot serve it. A composite B-tree is seekable only as a left prefix, and with no equality predicate on status the scan cannot treat updated_at as an ordering, because rows in the tenant's range are ordered by status first. Everything matching must be read and sorted before LIMIT 20 can apply, so a tenant with 400,000 rows pays 400,000 rows to return 20.
  4. Add (tenant_id, owner_user_id, updated_at DESC, resource_id DESC). Equality on the first two columns leaves the index ordered by updated_at within that pair, so the plan becomes an index scan that stops after 20 rows with no Sort node. PostgreSQL can scan a B-tree backwards, so the DESC markers matter only if the two sort columns ever disagree in direction; keeping them explicit documents the order the keyset cursor depends on.

Follow-up

  • The endpoint paginates with OFFSET. What does page 500 cost with your index, and what does the keyset version cost?
  • How would you have caught this before release, given that a 10,000-row seed database produces the same plan shape at an unnoticeable cost?

Suggested practice checklist

Each day targets one stage of the reported loop and practises specific reported questions, in roughly the order the stages come: algorithms first, then machine coding, fundamentals, design, and the culture fit discussion last.

Day 1 — Linked lists and the loop map

  • List the six reported stages and, for each, the question types you will practise for it; mark the stage you are weakest at
  • Write linked-list multiplication twice: the modular-integer version and the version that returns a list using digit arrays and carries
  • Write reversal in groups of k with a dummy head, and test k = 1, k equal to the length and k larger than the length

Deliverable: A one-page stage map with your weakest stage marked, and two tested linked-list solutions with their complexities noted.

Day 2 — Dynamic programming, graphs and trees

  • Write the weighted grid path DP, then reduce it to O(n) space and add path reconstruction
  • Write longest increasing subsequence in O(n^2), then in O(n log n) with predecessor indexes for reconstruction
  • Write directed cycle detection with three-colour DFS and with Kahn's algorithm, then run both on a graph that contains a self-loop
  • Solve one tree traversal problem, then one Dijkstra or trie problem, since trees are a reported common topic and those two are reported as less common

Deliverable: Three timed solutions, each with a written complexity note and the edge cases tested, plus one tree problem and one Dijkstra or trie problem.

Day 3 — Machine coding: a full build

  • Set up the IDE and project you will screen-share, and confirm it compiles and runs a test
  • Build the parking lot under a timer: entities, allocation and pricing strategies, a runnable demo and a lot-full test
  • Build the token-bucket rate limiter behind an interface with an injectable clock, and test the refill rule

Deliverable: A verified environment and two runnable projects with tests, plus notes on where you ran out of time.

Day 4 — Concurrency, modelling and refactoring

  • Write the LRU cache with a single lock, then a sharded version, and explain the compute-if-absent path for misses
  • Write the library schema with the partial unique index on open loans, plus the checkout and return logic
  • Take a tangled 80-line class, add characterization tests, and refactor it with Strategy and constructor injection

Deliverable: A thread-safe cache, a library schema with service code, and a before-and-after refactor with passing tests.

Day 5 — SQL and networking fundamentals

  • Write the purchases query, then the any-30-day-window variant with a window function
  • Run EXPLAIN ANALYZE on a slow three-table join in a local database and fix it step by step, then work through the latency debugging practice question
  • Explain TCP versus UDP, DNS resolution and normalization aloud, two minutes each, and record yourself

Deliverable: Two tested queries, a before-and-after query plan with notes, and three recorded explanations.

Day 6 — High-level design: chat, URL shortener and checkout

  • Design the chat backend: gateways, the connection registry, message storage, ordering and the offline sync path
  • Design the URL shortener: key generation, the caching layer, the 301 versus 302 choice and async analytics
  • Design the checkout API and schema with the idempotency key, the order state machine and stock reservation

Deliverable: Three design diagrams, each with an API sketch, a data model and a list of failure modes.

Day 7 — Consistency, payments and the culture fit discussion

  • Compare 2PC and sagas with the outbox on an order-payment-inventory flow, including what happens when a compensation fails
  • Walk through payment fault tolerance aloud, covering timeouts treated as UNKNOWN and the reconciliation job
  • Write four STAR stories (full ownership, a changed requirement, a production incident, a speed-versus-quality trade-off), make one of them show you explaining a decision to a non-engineer, and prepare the walkthrough of your most complex project
  • Work through the practice question on unblocking an engineer without taking the keyboard, and the one on estimating work you have never done

Deliverable: A consistency comparison table, a payment failure walkthrough, four STAR stories, one project walkthrough and written answers to two behavioral practice questions.

Behavioural preparation

Candidates describe the last stage as culture fit plus a deep dive into the resume, focused on owning work end to end, resilience, communication and coping with changing requirements. Because the role works with product and operations, include at least one story where you explained a technical decision to someone outside engineering. Every story needs a decision you made and a number that shows the result.

  1. Walk me through the architecture of your most complex recent project, highlighting the bottlenecks you overcame.
  2. Tell me about a feature you owned from design through production monitoring. What broke after launch, and how did you find it?
  3. Describe a time requirements changed late in a build. What did you cut, what did you keep, and how did you agree it with product?
  4. Tell me about a production incident you were responsible for fixing. How did you find the root cause, and what did you change so it would not happen again?
  5. Describe a technical trade-off you had to explain to a non-technical stakeholder. How did you frame it, and what did they decide?

Tell callers you do not own that their integration breaks

Medium · deprecation · compatibility · stakeholders

A field in a write endpoint's response must change shape. You own the endpoint; you do not own the four internal callers or the outbound webhook consumers who read it. Describe a deprecation you were responsible for: what you shipped first, how you established who was actually reading the field, the window you gave and what set its length, what you did about the consumer who never moved, and how you decided removal was safe. Name the signal you used, not the announcement you sent.

Approach

  1. Establish the reader set empirically rather than from a wiki of owners: per-field usage counters keyed by principal, or access logs attributed to a consumer. State the blind spot of whichever you pick, since a consumer that reads the field only on a monthly job will not appear in a week of logs.
  2. Ship additive first. Populate the new field alongside the old one so no reader is forced to move, which is also what keeps a rolling deploy safe, because old and new instances answer the same requests at the same time and a rollback must still find the old shape present.
  3. Set the window from the slowest legitimate consumer's release cadence, not from your calendar, and decide separately what to do for a consumer with no release process at all, such as an external webhook endpoint you can only email.
  4. Convert silence into evidence before you rely on it: a short, low-traffic removal window that makes a still-dependent consumer fail visibly and loudly while you are watching, rather than at three in the morning after you have moved on.

Follow-up

  • How would you detect a consumer that reads the field only during a monthly export?
  • One caller refuses to move and has a commercial relationship behind it. What changes in your plan and what does not?

Estimate work you have never done and defend the range

Hard · estimation · backfills · expand-contract

You are asked to estimate a change you have never attempted: add a column to a 100-million-row table, populate it, move reads across, and drop the old shape. Give a range with the assumptions that generate it, including batch size, the signal your backfill throttles on, and wall-clock hours, and name the three unknowns that would move the number most. Then describe a real estimate you gave under comparable ignorance: how you expressed its uncertainty, what you committed to, and how wrong you turned out to be.

Approach

  1. Decompose into independently deployable steps before estimating anything: add the column nullable, write both shapes, backfill in batches, verify, move reads, stop writing the old shape, drop it. That is four deploys spread over days, and the calendar estimate is dominated by them rather than by the loop's runtime.
  2. Do the arithmetic aloud for the part that has arithmetic in it: batch size times number of batches times per-batch duration, at a write rate the primary can absorb alongside roughly 1.2k writes per second of production traffic. The loop is throttled by replication lag and lock waits, not by how fast it can issue statements.
  3. Price the schema step by its lock rather than its statement duration. In PostgreSQL an ALTER TABLE taking ACCESS EXCLUSIVE waits for every open transaction on that table while later queries queue behind it, so a millisecond change issued during a thirty-second analytics query stalls that table for thirty seconds. Adding a nullable column with a non-volatile default avoids a rewrite from version 11; a new index wants CREATE INDEX CONCURRENTLY, which cannot run inside a transaction block and leaves an invalid index behind if it fails.
  4. Express the answer as a range whose endpoints each trace to a stated assumption, then name the cheapest experiment that collapses it, which is almost always running one real batch against the real table and multiplying.

Follow-up

  • How do you verify the backfill genuinely finished, given rows written by production traffic while it ran?
  • Where does the backfill resume from after a worker is killed mid-batch, and what makes that resume point trustworthy?

Unblock an engineer without taking the keyboard

Easy · mentoring · leases · at-least-once

A teammate has spent two days on a job handler that occasionally writes duplicate rows. They are certain the queue is delivering twice by mistake. You suspect a lease expiring under a slow handler, so the job is running concurrently with itself. Describe how you have unblocked someone in this position: what you asked before offering a hypothesis, what you showed them rather than told them, and what you left them owning. Then say what you would do if their theory turned out to be the right one.

Approach

  1. Ask before diagnosing, and ask for things answerable from data they already have: the attempt count on the job rows that produced duplicates, the handler's observed duration against its lease expiry, and whether the duplicate rows share a natural key that a unique constraint could have caught.
  2. Teach the shape rather than the answer. A lease cannot distinguish a dead worker from a slow one, so a handler that outruns its lease is running twice by design, and deploys deliver the other half by killing handlers mid-run on every rollout. Both of their candidate theories produce identical duplicate rows, which is why the evidence has to come from timings rather than from argument.
  3. Hand over a checklist they execute: a natural key on every write the handler performs so the second copy collides rather than appends, the record of intent written before any external effect, a lease heartbeat while running, and the metric that shows it working.
  4. Keep ownership with them deliberately. Pair on the first write, then step back; if you finish it yourself you have closed one ticket and left the same person stuck on the next redelivery.

Follow-up

  • How would you distinguish a genuine double-delivery from a lease expiry using only the data already stored?
  • Their handler calls an external endpoint before recording that it did. What do you tell them to change first?