cswork / libraryOpen practice workspace

Interview guides · C-edge

C-edge Software Engineer Interview Guide

C-edge Software Engineer interview guide: the three reported rounds, data structure, SQL and DBMS questions, and a 7-day preparation plan.

The 60-second brief

Candidates describe C-edge software engineers as working across backend services, database schemas and front-end interface components. Reports say little more about the product, customers or domain, so this guide does not guess at them. The role is described as owning features through their whole lifecycle: clarifying requirements with product managers, designing and writing the code, reviewing other engineers' changes, and working with operations teams when something breaks in production.

This guide is for candidates applying for a Software Engineer role at C-edge. The requirements candidates describe list Java or C++, strong SQL, and comfort with trees, graphs and linked lists, with Spring Boot, basic system design and cloud-based development as extras.

Candidates report three rounds over roughly 3-5 weeks: an Online Assessment, a series of Technical Interviews that can mix virtual and face-to-face sessions, and a final HR/Managerial Discussion.

Focus areas

  • Data Structures
  • Algorithms & Problem Solving
  • DBMS Fundamentals

Overview

The loop starts broad and then narrows. The first stage is reported to combine aptitude, coding and core CS fundamentals in one online assessment, so you need to move between a quantitative puzzle, a short coding task and a crisp factual answer. The technical interviews that follow are described as testing depth in specific domains, problem-solving speed and design challenges, with difficulty rising as you go. The last stage is a conversation about your projects, your fit and where you want your career to go.

The reported questions fall into three groups. The data structure group is classic: an LRU cache (hash map plus doubly linked list), finding the missing and repeating numbers in a large file of integers, level-order traversal of a binary tree (a queue), an anagram check (character counts), and loop detection in a linked list (two pointers). The core fundamentals group covers ACID, primary key versus unique key, constructors in a Singleton class, processes versus threads, and SQL joins. The design group is mostly data and object modelling: a schema for a LinkedIn-style or food delivery platform, indexing, normalization trade-offs, a class hierarchy for a library management tool, and one question on managing API integrations. It is not a large-scale distributed design set, so spend your design time on tables, keys, indexes and class boundaries.

Candidates also report a heavy SQL and DBMS emphasis: joins, subqueries and window functions in queries, plus indexing, normalization and transaction management in design discussions. Pattern-wise, sliding window, hashing, recursion and dynamic programming are the ones named. Choose Java or C++ (the languages the requirements list) and do all of your practice in it, because the Singleton, threading and class-design questions have language-specific answers.

Reports stress explaining why you made a technical choice, not just how, and starting a design answer by fixing scope and requirements. In practice that means each answer in this guide should end with a complexity statement and one alternative you rejected. Keep one or two resume projects ready to explain down to the schema and the technology internals, since the final stage is about past projects.

The C-edge interview process

Candidates report three stages over roughly 3-5 weeks. Reports do not say which reported question belongs to which stage, so the preparation below is organised by what each stage covers.

Online Assessment

Candidates report the opening stage as an online assessment mixing aptitude, coding and core CS fundamentals, used as a baseline screen before the technical interviews. Because reports do not map individual questions to it, treat the reported coding and fundamentals questions as one pool and prepare for breadth: you will switch between problem types with little warm-up, so practise doing exactly that.

What to demonstrate

  • Aptitude, as the stage is reported to include it
  • Coding that has to run correctly in your chosen language
  • Core CS fundamentals such as DBMS, OS and SQL basics

How to prepare

  • Write the five reported coding problems (LRU cache, missing and repeating numbers, level-order traversal, anagram check, loop in a linked list) from a blank file in Java or C++, and state the time and space complexity of each.
  • Turn the fundamentals questions (ACID, primary key versus unique key, SQL joins, processes versus threads) into short spoken answers that each include a concrete example.
  • Practise general quantitative and logical-reasoning sets separately, since aptitude is reported and no sample questions are.

Technical Interviews

Candidates report a series of technical interviews, possibly a mix of virtual and face-to-face sessions, testing depth in specific domains, problem-solving speed and design challenges, with difficulty increasing across the stage. The reported question types here are data structures and algorithms, DBMS and SQL, and design topics such as schemas, indexing and class design. Reports encourage thinking aloud and clarifying scope before drawing anything, so rehearse answers that start with requirements and end with trade-offs.

What to demonstrate

  • Depth in specific domains, including data structures and DBMS
  • Problem-solving speed on algorithm questions
  • How you handle design-related challenges and justify choices

How to prepare

  • For the schema question, write DDL for both a LinkedIn-style platform and a food delivery application, with keys, foreign keys and the indexes their main queries need.
  • Write SQL with joins, subqueries and window functions on small tables you build yourself, and read an EXPLAIN plan before and after adding an index.
  • Prepare the language-specific answers: a thread-safe Singleton and its constructor in Java or C++, and processes versus threads with a shared-counter race you can fix.
  • Outline a class hierarchy for a library management tool and an API-integration layer, and name one alternative design you rejected for each.

HR/Managerial Discussion

Candidates report the final stage as an HR or managerial discussion about past projects, cultural alignment and career aspirations. It is less about algorithms and more about how you explain your own work, so prepare stories with real decisions and numbers from your own experience. Reports also say that any technology listed on a resume can be questioned in detail.

What to demonstrate

  • Your account of past projects and your own contribution
  • Cultural alignment, as the stage is reported to cover it
  • Career aspirations

How to prepare

  • Write a walkthrough of two resume projects covering the problem, your role, the schema or architecture, one trade-off and what you would change.
  • For every technology on your resume, write down how it works underneath, for example how Spring Boot wires a request or why a database index speeds a query.
  • Prepare a short answer on where you want your career to go and how a Software Engineer role with product and operations collaboration fits it.

Where candidates lose points

Presenting only the optimised solution to a classic problem such as the anagram check or the missing and repeating numbers

State the baseline first (sorting the strings, or a nested scan for the numbers), give its complexity, then name the data structure that removes the bottleneck. Practise saying why the baseline is too slow.

An LRU cache that evicts correctly but updates recency wrongly, or scans a list to find the oldest entry

Use a hash map from key to node plus a doubly linked list with sentinel head and tail nodes so that get, put and eviction are all constant time. Test put on an existing key, get on a missing key and capacity one.

Answering the missing and repeating numbers question without stating assumptions about the range and the file size

Ask whether values run from 1 to n with one missing and one repeated, then pick the method to match: sum and sum of squares, XOR partitioning or index marking in memory, and a bitset or chunked counting pass when the file cannot be loaded. Mention overflow.

Defining ACID, joins or normalization in textbook terms with no example, or designing a schema without naming the queries it must serve

Anchor each definition to a concrete case, such as a money transfer for ACID or an order and order-items table for normalization. In schema answers list the three or four main queries first, then choose keys and indexes for them.

Listing technologies on your resume that you cannot explain beneath the surface

Cut the resume to what you can defend. For each remaining item prepare how it works, why you chose it and what went wrong with it, since the final discussion is about past projects.

Questions & practice

How would you implement an LRU cache?

Medium · Data Structures & Algorithms

Approach

  1. Pair a hash map with a doubly linked list ordered by recency: the map goes from key to list node, the head holds the most recently used entry and the tail the least recently used one. Both get and put run in O(1) time, and space is O(capacity).
  2. get: look the key up in the map. On a miss, return the miss value (often -1). On a hit, unlink the node, move it to the head and return its value.
  3. put: if the key already exists, update the value and move the node to the head without evicting anything. Otherwise insert a new node at the head, and if the size now exceeds capacity, unlink the tail node and delete its key from the map. Each node stores its key so eviction can find the map entry.
  4. Dummy head and tail sentinel nodes remove the null checks for an empty list or a one-element list. In C++ I would use std::list plus unordered_map<key, list::iterator> and move entries with splice. In Java, LinkedHashMap(capacity, 0.75f, true) with removeEldestEntry overridden behaves the same way, but be ready to build the version by hand.
  5. Edge cases: capacity 0 or 1, a put on an existing key (refreshes recency, never evicts), and a get that must count as a use.

Follow-up

  • How would you make this thread-safe? A concurrent map alone is not enough, because every get also changes the list.
  • How would you change it to LFU, or add a per-entry TTL?
  • What does eviction look like if entries have different sizes and the limit is in bytes?

Can you find the missing and repeating numbers in a large file of integers?

Medium · Data Structures & Algorithms

Approach

  1. Pin down the input first: n values that should be exactly 1..n, with one value missing and one value appearing twice. A boolean or count array solves it in one pass with O(n) time and O(n) extra space; sorting first costs O(n log n).
  2. Math version: let d = sum(values) - n(n+1)/2 = R - M and q = sum(squares) - n(n+1)(2n+1)/6 = R^2 - M^2. Then R + M = q / d, which gives both values in O(1) extra space. The sum of squares grows like n^3/3 and overflows a signed 64-bit integer once n is around 3 million, so use __int128, BigInteger or the XOR method on large files.
  3. XOR version: XOR every value with every number in 1..n to get R ^ M. Pick any set bit of that result and split both the values and 1..n into two buckets by that bit; XOR-ing each bucket gives the two candidates. Check which candidate actually occurs in the file, or use the sign of the plain sum difference, to tell the repeated value from the missing one. Nothing overflows.
  4. For a file too large for memory, stream it. A bitmap of n bits (125 MB for n = 10^9) finds the repeat when a bit is seen set a second time and the missing value as the one bit left clear. The XOR method needs only two sequential passes and O(1) memory.
  5. If the input is an in-memory array you may modify, marking visits by negating values at their index gives O(1) extra space. Say that this mutates the caller's data.

Follow-up

  • What changes if k values are missing and k values are repeated?
  • What if the values are not guaranteed to fall in 1..n?
  • How would you split this across several machines, each holding part of the file?

Explain the logic behind performing a level-order traversal of a binary tree.

Medium · Data Structures & Algorithms

Approach

  1. Level-order traversal is breadth-first search with a FIFO queue. Seed the queue with the root. While the queue is not empty, record its current size, pop exactly that many nodes, collect their values as one level, and enqueue each node's left child and then its right child.
  2. Capturing the size before draining a level is what separates levels without marker nodes. Time is O(n), since every node is enqueued and dequeued once. Space is O(w), where w is the widest level, which can be about n/2 for a complete tree.
  3. Edge cases: a null root returns an empty list, and null children must not be enqueued. In Java, ArrayDeque throws a NullPointerException if you offer null, so check before you offer.
  4. A depth-first alternative passes the depth down the recursion and appends each value to result[depth]. It gives the same grouping using O(h) stack space, but a skewed tree can make h equal n and overflow the call stack.
  5. Know the common variants: zigzag order (reverse alternate levels, or use a deque), right-side view (last node of each level), level averages, and minimum depth (BFS can stop at the first leaf).

Follow-up

  • How would you print the levels in zigzag order, or from the bottom level up?
  • How does this change for an n-ary tree, or for a graph that can contain cycles?
  • Can you do it without a queue, and what does that cost?

How do you check if two strings are anagrams of each other?

Medium · Data Structures & Algorithms

Approach

  1. Clarify first: is the comparison case-sensitive, do spaces and punctuation count, and is the input ASCII or arbitrary Unicode? If the lengths differ after normalising, return false immediately.
  2. Sorting both strings and comparing them is O(n log n) time and needs O(n) space for the character arrays. It is easy to get right and makes a good first answer.
  3. The optimal version for a fixed alphabet uses one int[26] (or int[256]) array: increment for each character of the first string, decrement for each character of the second, then check that every count is zero. That is O(n) time and O(1) extra space. Decrementing in the same array avoids a second table.
  4. For Unicode, count code points in a hash map rather than UTF-16 chars: in Java, iterate String.codePoints() so surrogate pairs are not split. Mention that composed and decomposed accents only compare equal after normalisation (java.text.Normalizer).
  5. Edge cases: two empty strings are anagrams, null inputs need a defined answer, and the same letters with different counts ('aab' and 'abb') must fail, which a set-based check gets wrong.

Follow-up

  • Group a list of words into anagram sets. What key would you use, and what does it cost?
  • Find every start index in a long text where a substring is an anagram of a pattern (sliding window over counts, O(n)).
  • How would you compare two strings too large to hold in memory?

Implement a function to detect a loop in a linked list.

Medium · Data Structures & Algorithms

Approach

  1. Use Floyd's tortoise-and-hare algorithm. Slow moves one node per step and fast moves two. If fast or fast.next becomes null, there is no loop; if slow == fast, there is one. The loop condition is while (fast != null && fast.next != null). Time is O(n) and extra space is O(1).
  2. The simple alternative stores visited nodes in a hash set, compared by node identity rather than by value because values can repeat. It is O(n) time and O(n) space, and it finds the first node of the loop directly.
  3. To find where the loop starts, reset one pointer to the head after the meeting and advance both pointers one node at a time; they meet at the loop's first node. This works because the distance from the head to the start equals the distance from the meeting point to the start, modulo the loop length.
  4. Loop length: from the meeting point, step one pointer until it returns. To remove the loop, find the node whose next is the start node and set its next to null.
  5. Edge cases: an empty list, a single node whose next points to itself, and a loop that returns to the head.

Follow-up

  • Why can the fast pointer never skip past the slow pointer inside the loop?
  • Return the node where the loop begins, then remove the loop.
  • How would you detect whether two linked lists intersect when either of them may contain a loop?

Describe the difference between processes and threads in an operating system.

Medium · Core Computer Science Fundamentals

Approach

  1. A process is a running program with its own virtual address space, open file descriptors and other OS resources. Processes are isolated from each other, so one crashing does not corrupt another.
  2. Threads are execution paths inside one process. They share the heap, global data, code and open files, while each thread has its own stack, registers, program counter and thread-local storage.
  3. Cost: creating a process (fork, which is copy-on-write) and switching between processes means switching address spaces, page tables and often TLB entries, so it costs more than creating or switching threads within one process. On Linux, both are tasks created through clone() with different sharing flags.
  4. Communication: processes need IPC (pipes, sockets, shared-memory segments, message queues). Threads communicate through shared memory directly, which is cheaper but brings race conditions and deadlocks unless shared state is protected with mutexes, atomics or immutable data.
  5. When to use which: choose processes for isolation and fault containment, such as browsers running tabs as separate processes. Choose threads, usually a pool, for many concurrent tasks over shared in-memory state, such as a Java server. In Java, platform threads map one-to-one to OS threads, while Java 21 virtual threads are scheduled onto a small set of them.

Follow-up

  • What happens to a multithreaded process's other threads when one thread calls fork()?
  • What four conditions are required for a deadlock, and how would you break one of them?
  • What is the difference between user-level (green or virtual) threads and kernel threads?

What are the common types of SQL joins and when should you use each?

Medium · Core Computer Science Fundamentals

Approach

  1. INNER JOIN keeps only rows that match on both sides; use it when a row is meaningless without its partner (order lines with their products). LEFT JOIN keeps every row of the left table and fills the right side with NULLs when there is no match; use it when missing partners matter. RIGHT JOIN is the mirror image and is usually rewritten as a LEFT JOIN for readability.
  2. Anti-join pattern: customers with no orders is LEFT JOIN orders ... WHERE o.id IS NULL, or NOT EXISTS. Avoid NOT IN on a nullable column: a single NULL in the subquery makes the predicate unknown for every row, so the query returns nothing.
  3. A filter on the right table placed in WHERE removes the NULL rows and silently turns a LEFT JOIN into an INNER JOIN. Put that condition in the ON clause when you want to keep unmatched left rows.
  4. FULL OUTER JOIN keeps unmatched rows from both sides, which suits reconciling two sources. MySQL has no FULL OUTER JOIN, so emulate it with a LEFT JOIN UNION a RIGHT JOIN. CROSS JOIN is the Cartesian product (m x n rows), useful for generating combinations such as every date with every store. A self join compares rows of one table, as in employee to manager.
  5. Before aggregating, check the join cardinality: joining a one-to-many table and then summing a column from the one side inflates the totals. Pre-aggregate in a subquery or CTE, then join.

Follow-up

  • Write the query for users who signed up but never placed an order, in two different ways.
  • Why did adding WHERE right_table.status = 'active' change the row count of a LEFT JOIN?
  • How does the database physically execute a join (nested loop, hash join, merge join), and when does it pick each?

How would you handle indexing to optimize query performance?

Medium · System Design & Architecture

Approach

  1. Start from the slow query, not from the table. Run EXPLAIN, or EXPLAIN ANALYZE in PostgreSQL, compare estimated with actual row counts, and see whether the plan uses a sequential scan, an index scan or an expensive sort.
  2. A B-tree index, the default, serves equality, ranges, ORDER BY and prefix LIKE 'abc%'. For a composite index the leftmost-prefix rule applies: put equality columns first and the range or sort column last, so (user_id, created_at) serves WHERE user_id = ? ORDER BY created_at DESC without a sort.
  3. A covering index includes every column the query reads (INCLUDE in PostgreSQL 11+ and SQL Server, or extra key columns), so the engine can answer from the index alone. A partial index (WHERE status = 'open') stays small when queries only touch a subset of rows.
  4. Know what makes an index unusable: a function on the column (WHERE LOWER(email) = ?) unless there is a matching expression index, a leading wildcard (LIKE '%abc'), an implicit type cast, or low selectivity, where the planner rightly prefers a sequential scan.
  5. Every index slows INSERT, UPDATE and DELETE and takes storage. Drop indexes that are never used (in PostgreSQL, idx_scan = 0 in pg_stat_user_indexes), and on a live table build new ones without blocking writes (CREATE INDEX CONCURRENTLY in PostgreSQL).

Follow-up

  • You added an index and the planner still does a sequential scan. What are the possible reasons?
  • What is the difference between a clustered and a non-clustered index, and how does it affect range queries?
  • When would you use a hash, GIN or full-text index instead of a B-tree?

What are the trade-offs between different database normalization levels?

Medium · System Design & Architecture

Approach

  1. Define the levels precisely. 1NF: atomic values and no repeating groups. 2NF: no non-key column depends on only part of a composite key. 3NF: no non-key column depends on another non-key column. BCNF: every determinant is a candidate key.
  2. Normalising buys integrity. It removes update anomalies (a customer's city copied onto every order row must be changed everywhere), insertion anomalies (you cannot store a product until it is ordered) and deletion anomalies (deleting the last order loses the customer), and it keeps writes small.
  3. It costs reads: more joins per query, and join-heavy reporting queries that are slower to run and to write. Read-heavy or analytical workloads often denormalise deliberately, for example star schemas, precomputed counters or a copied display name.
  4. Denormalisation moves the cost into writes. Every copy needs a sync mechanism (same transaction, trigger, or async job), and each has a consistency window or a contention cost. A counter updated by many concurrent writers becomes a hot row.
  5. Not every duplicate is a defect. An order line's unit price at purchase time is a historical fact, not redundant data, so storing it is correct even in a fully normalised design. Use an example like this to show you are reasoning about dependencies, not following rules.

Follow-up

  • Give a table that is in 3NF but not in BCNF, and say how you would decompose it.
  • When would you denormalise in an OLTP schema, and how would you keep the copy correct?
  • How would you keep a denormalised like count accurate under heavy concurrent writes?

Explain the ACID properties of a database transaction.

Medium · Core Computer Science Fundamentals

Approach

  1. Anchor the answer in one example, a transfer that debits one account and credits another, and define each property against it. Atomicity: both writes commit or neither does. Engines enforce this with an undo or write-ahead log, and a failure mid-transaction rolls back.
  2. Consistency: a committed transaction moves the database from one valid state to another with respect to declared constraints (primary key, foreign key, unique, CHECK). Say that business rules the schema cannot express remain the application's job.
  3. Isolation: concurrent transactions behave as if they ran in some serial order, to the degree the isolation level promises. Read Uncommitted, Read Committed, Repeatable Read and Serializable progressively rule out dirty reads, non-repeatable reads and phantoms. Engines implement this with locking (two-phase locking) or MVCC snapshots. PostgreSQL defaults to Read Committed and MySQL InnoDB to Repeatable Read.
  4. Durability: once a commit is acknowledged, it survives a crash, because the log record is flushed to stable storage (fsync) before the acknowledgement and is replayed on recovery.
  5. Close with the trade-off: stronger isolation costs throughput through more blocking, aborts or retries, so the level should match the anomaly the workload cannot tolerate.

Follow-up

  • Which isolation level prevents a lost update in a read-modify-write, and what else would you use (SELECT ... FOR UPDATE, an optimistic version column)?
  • How does a write-ahead log provide both atomicity and durability?
  • What happens to ACID guarantees when one operation spans two databases or services (two-phase commit, sagas)?

What is the difference between a primary key and a unique key?

Medium · Core Computer Science Fundamentals

Approach

  1. A primary key identifies each row. A table has at most one, it can be a single column or composite, and its columns are implicitly NOT NULL. Foreign keys in other tables normally reference it.
  2. A table can have many unique keys. They enforce no duplicates on non-null values, but NULL handling differs by engine. PostgreSQL and MySQL allow multiple NULLs, because NULL is not equal to NULL. SQL Server's unique constraint allows only one NULL. PostgreSQL 15 added UNIQUE NULLS NOT DISTINCT to change this.
  3. Both create a unique index automatically. In MySQL InnoDB, the primary key is the clustered index, so rows are stored in key order and every secondary index carries the primary key value. SQL Server clusters on the primary key by default. A PostgreSQL table is a heap with no clustered index.
  4. Concrete example: users.id as a surrogate primary key, and users.email under a unique constraint as the natural identifier, so an email can change without touching any foreign key.
  5. A foreign key may reference a unique constraint as well as a primary key. Say so, because the claim that only primary keys can be referenced is a common wrong answer.

Follow-up

  • Surrogate or natural primary key: what does each choice cost when the natural value changes?
  • How does the choice of primary key affect insert performance in a clustered table (random UUIDs versus sequential ids)?
  • How would you enforce uniqueness of email only among active users (a partial or filtered unique index)?

How do constructors work in a Singleton class?

Medium · Core Computer Science Fundamentals

Approach

  1. A singleton makes the constructor private, so no outside code can call new. It holds the single instance in a static field and exposes a static accessor such as getInstance(). The constructor runs exactly once, the first time the instance is created.
  2. Eager initialisation (private static final INSTANCE = new X()) is thread-safe because JVM class initialisation is synchronised, but it constructs the object even if nobody uses it. For lazy initialisation, the holder idiom puts the instance in a private static nested class: it is lazy and thread-safe without explicit locking.
  3. If you write double-checked locking in Java, the field must be volatile. Without volatile, the Java memory model allows another thread to see a reference to a partially constructed object.
  4. Ways to break it: reflection can call the private constructor after setAccessible(true), so throw from the constructor if an instance already exists; deserialisation creates a new object unless readResolve() returns the existing instance. An enum singleton handles both cases. In C++, delete the copy constructor and copy assignment, and use a function-local static (Meyers singleton), whose initialisation is thread-safe since C++11.
  5. Trade-off: a singleton is global state, which makes unit tests order-dependent and hides dependencies. Injecting one shared instance through a container (a Spring bean is singleton-scoped per container by default) keeps the single instance and stays testable.

Follow-up

  • Make your lazy version thread-safe and explain why volatile matters.
  • How could reflection or serialisation produce a second instance, and how do you prevent it?
  • How would you unit-test code that calls getInstance() directly?

Design a database schema for a platform like LinkedIn or a food delivery application.

Medium · System Design & Architecture

Approach

  1. Pick one of the two and scope it aloud. For food delivery, list the core flows (browse a restaurant's menu, place an order, assign a courier, track status, pay) and derive the entities from them: users, addresses, restaurants, menu_items, orders, order_items, couriers, deliveries and payments.
  2. Key decisions to state: order_items stores quantity and unit_price at order time, because menu prices change and historical orders must not. Order status is a constrained enum, with an order_status_history table (order_id, status, changed_at) so the timeline survives updates.
  3. For the LinkedIn variant: users and profiles; positions(user_id, company_id, title, start_date, end_date); a user_skills junction table for the many-to-many relationship; posts; and connections. Store an undirected connection once with CHECK (user_a < user_b), or as two directed rows so a single index on user_id answers 'my connections'. Name which one you chose and why.
  4. Design indexes from the queries: orders(user_id, created_at DESC) for order history, orders(restaurant_id, status) for a restaurant's open orders, and foreign keys indexed for joins. Start normalised to third normal form and denormalise only measured hot reads, such as a connection count or a restaurant's average rating, saying how each copy stays in sync.
  5. Scale: say what grows fastest (orders, or connections and posts) and the partition key you would use, and name the query it makes expensive. Sharding orders by user makes per-restaurant queries cross-shard. Second-degree connections are a self join that gets costly at scale, so a cache or graph store is the next step.

Follow-up

  • Write the query for second-degree connections, or for a restaurant's revenue per day.
  • How would you model an order that is cancelled after a courier has been assigned?
  • Which table would you shard first, by which key, and what breaks?

Explain how you would design a system to manage API integrations.

Medium · System Design & Architecture

Approach

  1. Clarify direction and scope: outbound calls to third-party providers (payments, SMS, shipping), inbound webhooks from them, or both. Ask how many providers there are and whether a failed call blocks a user request or can complete later.
  2. Core structure: one internal interface per capability, with an adapter for each provider behind it (adapter or strategy pattern). Each adapter maps the provider's schema and error codes into an internal model, so a provider change stays inside one class. Keep credentials in a secret store and per-provider settings in config.
  3. Reliability: give every outbound call a timeout. Retry only retryable failures (timeouts, 429, 5xx), with exponential backoff and jitter, never 4xx validation errors. Send an idempotency key so a retry after a timeout cannot apply the operation twice, and put a per-provider circuit breaker and rate limiter (token bucket) in front.
  4. Run non-interactive calls through a job queue with workers, with a dead-letter queue for exhausted retries and a replay tool. For inbound webhooks, verify the signature (typically an HMAC), store the event, acknowledge with a 2xx quickly, process asynchronously, and deduplicate by event id because providers redeliver.
  5. Observability: log every call with a correlation id, keep latency, error-rate and retry metrics per provider, and alert on breaker state. The trade-off to state is that asynchronous processing gains resilience but gives callers eventual results, so expose a status they can poll or receive.

Follow-up

  • A provider is down for an hour. What happens to queued work, to users and to the retry backlog?
  • A request timed out but the provider may have processed it. How do you find out and avoid a duplicate?
  • How would you add a new provider, or switch traffic between two, without a code deploy?

How do you approach designing a class hierarchy for a complex system like a library management tool?

Medium · System Design & Architecture

Approach

  1. Scope the use cases first: search the catalogue, borrow, return, renew, reserve, pay fines, and the actors (member, librarian). Derive classes from these flows rather than listing every noun.
  2. Separate the title from the physical copy. A Book (ISBN, title, authors) has many BookItem copies (barcode, status, location), and a loan or reservation refers to a copy, not to the title. Missing this distinction breaks borrowing as soon as a library holds two copies of one book.
  3. Prefer composition over inheritance. Use an abstract LibraryItem only where behaviour really differs by type (Book, DVD, Magazine). Model loan rules as a LoanPolicy strategy (loan days, item limit, fine rate) per member type, rather than subclasses such as StudentMember, so a new rule does not need a new class.
  4. Keep state transitions inside the objects that own them. BookItem status moves AVAILABLE -> LOANED -> AVAILABLE, with RESERVED and LOST as side states. Loan(item, member, dueDate, returnedDate) is created by a checkout service that validates the member's limit and the item's status. FineCalculator stays separate (single responsibility), and an observer notifies the next member in a reservation queue on return.
  5. Name the concurrency edge case: two members checking out the last copy at once. Make the status change atomic with a conditional update or a version check, so exactly one succeeds.

Follow-up

  • How would you add e-books with unlimited simultaneous loans without changing the checkout code?
  • Fine rules differ for students and staff and change every year. Where does that logic live?
  • Two members reserve the same last copy at the same moment. Who gets it, and how does your design guarantee that?

Read latency spikes on a sixty-second sawtooth

Medium · cache stampede · single-flight · ttl

The cached listing read path serves about 14k reads/second at an 85% hit rate. p99 sits at 35 ms for 57 seconds, jumps to 900 ms for 3, and repeats. During each spike the primary shows several hundred identical listing queries starting within the same millisecond, all carrying one large tenant's id. Cache entries use a 60-second TTL. Give the mechanism, the ordered checks, the fix, and the correctness hazard your fix must not introduce.

Approach

  1. Match the period to a configured number before theorising about load. A spike every 60 seconds against a 60-second TTL is an entry expiring, and you confirm it by correlating spike timestamps with the entry's write time rather than with the traffic curve. If the period had matched a cron or a GC interval instead, this is a different investigation.
  2. Establish the concurrency of the miss. Several hundred identical queries in one millisecond means the miss path has no coalescing: every request that arrives between expiry and repopulation recomputes. The herd size is that key's arrival rate times its recompute time, so at 1.2k reads/second for the hot key and a 250 ms recompute you expect about 300 concurrent misses, which matches what is observed.
  3. Add single-flight on the miss path so one caller per key recomputes under a short-lived lock while the rest wait for its result. Prefer stale-while-revalidate where the read tolerates it: return the expired value immediately and refresh asynchronously, which removes the latency spike rather than serialising it into a queue of waiters.
  4. De-synchronise the keys. Write TTLs with jitter, for example 60 seconds plus or minus 10%, so a deploy or a mass invalidation does not align every key on the same second and turn a per-key herd into a fleet-wide one.

Follow-up

  • The same sawtooth appears on a key that is invalidated on write rather than expired. Is that the same bug?
  • How does your answer change if the recompute takes 4 seconds instead of 250 ms?

Suggested practice checklist

Seven days that follow the loop: linked structures and trees first, then patterns and aptitude, SQL and DBMS, object and schema design, and finally a mock plus the project conversation.

Day 1 — Linked structures: LRU cache and cycle detection

  • Choose Java or C++ and set up a template file with a small test harness that you will reuse all week.
  • Implement an LRU cache with a hash map and a doubly linked list with sentinel nodes, with constant-time get and put.
  • Implement linked-list loop detection with two pointers, then extend it to return the node where the loop starts.

Deliverable: A working LRU cache and a loop detector with start-node extension in your language, each with tests for empty, single-node and capacity-one cases.

Day 2 — Trees, strings and counting problems

  • Write level-order traversal of a binary tree with a queue, then a version that returns one list per level.
  • Write the anagram check with a count array and a hash-map version for general characters, and compare with the sorting approach.
  • Solve the missing and repeating numbers problem in memory three ways (sum and sum of squares, XOR, index marking), then outline a chunked or bitset version for a file that does not fit in memory.

Deliverable: Three solved problems with the complexity of each stated, plus a written list of the assumptions you would confirm before coding the missing and repeating numbers task.

Day 3 — Patterns and aptitude

  • Solve one problem each in sliding window, hashing, recursion and dynamic programming, writing the brute-force version first and the optimised version second.
  • Do a set of general aptitude problems (quantitative and logical reasoning) and log every error by type.
  • Re-implement your weakest problem from days 1 and 2 from scratch without notes.

Deliverable: Four pattern problems each with a brute-force and an optimised solution and complexities, plus an error log from the aptitude set.

Day 4 — SQL: joins, subqueries, window functions, keys

  • Build two small tables with unmatched rows on both sides and write inner, left, right and full joins, plus an anti-join for rows with no match.
  • Write a correlated subquery and rewrite it with a window function (top N per group, running total).
  • Create a table with a primary key and a unique column, test NULL and duplicate inserts, and write down how your database treats NULLs in the unique column.

Deliverable: A file of queries with expected outputs and a half-page comparing a primary key with a unique key.

Day 5 — Transactions, normalization, indexing and schema design

  • Explain ACID with a money-transfer example and name what each isolation anomaly (dirty read, non-repeatable read, phantom) looks like.
  • Normalise an unnormalised orders table to third normal form, then list where you would denormalise and what it costs on writes.
  • Create a table with enough rows to matter, run EXPLAIN on a slow query, add a composite index and compare plans.
  • Design a schema for a LinkedIn-style platform or a food delivery app: tables, keys, indexes and the main queries.

Deliverable: DDL for one platform, three key queries with the index each uses, an EXPLAIN before and after, and a one-page ACID and normalization trade-off sheet.

Day 6 — OS, Singleton, class design and API integrations

  • Write a shared-counter race with threads, fix it with a lock or atomic, and write down the process versus thread differences it illustrates.
  • Write a thread-safe Singleton in your language and explain the private constructor, lazy versus eager creation and why double-checked locking needs the right memory semantics.
  • Sketch a class hierarchy for a library management tool, deciding between a book and a physical copy, and where composition beats inheritance.
  • Outline a layer that manages API integrations: per-provider adapters, credential storage, retries with backoff, idempotency keys, rate limits and error handling.

Deliverable: A class diagram, working Singleton and race-fix code, and a one-page API integration outline.

Day 7 — Mixed mock and the project conversation

  • Run a full mock: one coding problem, one SQL query and one schema question from your weakest earlier days, answered aloud with complexity and trade-offs.
  • Write two project walkthroughs and a short career-aspirations answer, and check you can explain every technology on your resume.

Deliverable: A scored mock log, two project walkthroughs, a career-aspirations answer and written notes on the practice drills you completed.

Project and career conversation

The final stage is reported as an HR or managerial discussion on past projects, cultural alignment and career aspirations, so behavioural preparation here is mostly project storytelling. The role is also described as involving code reviews, work with product managers and help with production issues, so pick stories that show those. State your own actions and the outcome in each story.

  1. Walk through a project on your resume: what you owned, the hardest technical decision, the trade-off you made and what you would change.
  2. Describe a production problem you helped resolve with an operations or support team: how you diagnosed it, what you fixed and what you changed afterwards.
  3. Tell me about a time you disagreed with a reviewer or a product manager over scope, a timeline or code quality, and how it was resolved.
  4. Describe a time you took on technical debt or a long-term architectural cleanup while still delivering a feature.
  5. Where do you want your career to go in the next few years, and how does this role fit?
  6. Tell me about a time you picked up an unfamiliar technology quickly and what you built with it.

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?

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?

Turn a code review disagreement into a decision

Easy · code review · optimistic concurrency · communication

A colleague's change updates a row with UPDATE resource SET version = version + 1 WHERE resource_id = $1 AND version = $2 and treats an affected-row count of zero as a successful no-op. You read that as a silently lost update; they think returning 200 is friendlier to clients than returning a conflict. Describe how you have handled a review disagreement of this shape: what goes in the comment, when you leave the thread, and who decides. Then write the comment you would leave here, in under 80 words.

Approach

  1. Sort the disagreement before writing anything. A silently discarded write is a correctness claim about data; the choice between 409 and 412 is taste. Only the first justifies blocking a merge, and saying which one you are doing is most of the value of the comment.
  2. Make the claim reproducible in the comment itself with an interleaving rather than a principle: A reads version 7, B reads version 7, B commits version 8, A's predicate matches zero rows, A is told it succeeded and A's edit is gone.
  3. Offer the alternative with its cost attached: return 409 carrying the current version and the revision that won, so the client can re-read and re-apply. Note that automatic retry is not the fix, because a retry re-reads the winner's state and reapplies an intent formed against data that no longer exists.
  4. Apply an escalation rule you can state: two round trips on the thread, then a call, and the service's owner decides rather than the reviewer. A reviewer who cannot be overruled is a bottleneck with extra steps.

Follow-up

  • Where would you put the test that fails if someone reintroduces the swallowed zero rowcount?
  • The author says clients cannot handle a 409. How do you check whether that is true?