cswork / libraryOpen practice workspace

Interview guides · Arrcus

Arrcus Software Engineer Interview Guide

Arrcus Software Engineer interview guide: the two reported rounds, C and networking questions, sorting and tree problems, and a 7-day prep plan.

The 60-second brief

Candidates describe Arrcus as a company working on network infrastructure software, with routing, switching and cloud-native networking named as the areas. A Software Engineer there is described as designing, building and testing features for routing and switching code, taking a module from first design through deployment, reviewing teammates' changes, debugging low-level system problems and joining architecture discussions. The listed must-haves are C or C++, data structures and algorithms, networking protocols and system-level debugging.

This guide is for candidates interviewing for a Software Engineer position at Arrcus. It covers the two reported rounds, the twelve questions candidates report (classic coding problems, networking and systems fundamentals, and verbal problem solving), and a seven-day plan. Because the reported requirements centre on C or C++ and networking protocols, the preparation here assumes you will write and explain low-level code. Cloud-native networking, high-throughput or low-latency experience and scripting for automation are listed as nice-to-haves, so they get less room than the fundamentals.

Candidates report two stages over roughly 2-4 weeks: a Technical Assessment that sets a baseline for coding skill, then back-to-back Technical Interviews covering breadth and specific expertise.

Focus areas

  • Data Structures
  • Algorithms
  • Problem Solving

Overview

The work candidates describe is network software: code that sits close to the operating system and the network interface, where memory use, pointer handling and per-packet cost matter. That shapes the interview preparation. Expect the conversation to move from a short algorithm to what the machine is doing underneath it, so a solution that is correct but unaware of allocation, cache behaviour or buffer limits will feel incomplete.

The twelve reported questions fall into three groups. The coding group is short and classic: why Quicksort suits arrays while Merge Sort suits linked lists, printing the left view of a binary tree, reversing each word in a string, summing the elements of a matrix and computing the distance between two points. The fundamentals group covers what happens after a URL is typed into a browser, how network layers interact, memory management strategies in C and optimizing for high-throughput network environments. The problem-solving group covers debugging a memory leak in a production system, an algorithm for concurrent data requests and data structure trade-offs in a real-time system.

Several of the coding questions are small, which makes the details the whole question: empty input, repeated spaces, integer overflow in a sum, a null pointer, an off-by-one in a loop bound. Practise them in C or C++ and say your assumptions before you type. Candidates also report that solutions may be worked on a whiteboard or explained aloud, so rehearse without an editor, compiler or autocomplete.

The fundamentals and problem-solving questions are open-ended and are answered by talking. Build each answer as a short chain of concrete steps (which layer, which system call, which data structure, which measurement) rather than a list of buzzwords. Candidates report that networking theory is a core part of the technical rounds, so give it real study time alongside the algorithms.

The Arrcus interview process

Candidates report two stages over roughly 2-4 weeks, a Technical Assessment followed by Technical Interviews. This is what candidates describe, not a published process.

Technical Assessment

Candidates describe the Technical Assessment as the opening stage, used to set a baseline for your coding skill. A coding stage tests working code, so treat it as a place to produce correct, compilable solutions and explain their cost. The coding questions in this guide (sorting trade-offs, the binary tree left view, reversing words, matrix sum, point distance) are the practice material for this kind of stage. Pick C or C++, the languages the role lists first, and stay in one language so you are not searching for syntax.

What to demonstrate

  • Working code in a systems language, including the edge cases of the input
  • Choosing a data structure to fit the access pattern and stating its time and space cost
  • Explaining why one approach beats another, for example array versus linked list for sorting

How to prepare

  • Write the left view of a binary tree twice in C or C++, once with a level-order queue and once with a depth-first walk that records the first node seen at each depth, and compare their space use.
  • Implement reverse-each-word on a mutable char buffer in place, handling leading, trailing and repeated spaces and an empty string, and state that it runs in O(n) time with O(1) extra space.
  • Write a one-minute spoken answer for why Quicksort is preferred for arrays and Merge Sort for linked lists, naming random access, cache locality, stability and worst case.

Technical Interviews

Candidates describe the Technical Interviews as back-to-back sessions testing breadth of knowledge and specific expertise, with networking and systems fundamentals reported as a core component and some problems solved verbally while you explain your reasoning. Prepare to move between topics without warming up: a packet's path through the stack, how C manages memory, where a service loses throughput, how you would chase a leak. Answer each by naming the concrete mechanism, then the trade-off, then how you would measure it.

What to demonstrate

  • Networking theory: protocol stacks, the OSI model and how packets move through a network
  • System internals: how OS-level processes interact with network interfaces, and how memory is managed in C
  • Systems-level debugging and concurrency reasoning
  • Clear verbal problem solving, with stated assumptions and trade-offs

How to prepare

  • Prepare a layer-by-layer walkthrough of what happens after a URL is typed into a browser: DNS lookup, TCP handshake, TLS, HTTP request and response, with the frame, packet and segment headers added at each layer.
  • Write out a debugging sequence for a growing-memory production process: confirm the growth from resident memory over time, separate a true leak from a cache or fragmentation, then use a heap profiler or sanitizer on a reproduction.
  • Sketch a concurrent request handler (bounded queue, worker threads, sharded locks) and list where it contends, what it does when the queue is full, and how you would check it with a load test.
  • List six data structures with their worst-case operation cost and allocation behaviour, to answer real-time trade-off questions with numbers.

Where candidates lose points

Answering the Quicksort versus Merge Sort question with "Quicksort is faster" and nothing else

Give the properties that decide it. Quicksort partitions in place and walks contiguous memory, which suits arrays; its worst case is O(n^2) without a good pivot choice. Merge Sort needs no random access, merges linked nodes by relinking pointers with O(1) extra space, is stable and keeps O(n log n) in the worst case. End with when you would flip the choice.

Writing the small string and matrix problems as if the input were always well-formed

Say your assumptions first, then handle the empty string, runs of spaces, a null pointer, a zero-row matrix, and a sum that can overflow int (use a wider accumulator). In C, also state who owns the buffer and whether the function may modify it.

Naming the networking layers as a list without showing how data moves through them

Follow one request from the browser to the server and back: name resolution, connection setup, what each layer adds to the packet, how the next hop is chosen, and where the receiving operating system hands the data to a process. A traced example shows you understand the interaction between layers.

Answering the production memory-leak question with "run Valgrind"

Open with how you confirm and bound the problem: resident memory over time, which process, whether it tracks load. Separate leaks from caches and fragmentation, use a low-overhead heap profiler or allocation snapshots on a live process, move to a sanitizer on a reproduction, and say how you would verify the fix.

Saying "add a mutex" for the concurrent data requests question

Identify the shared state first and whether reads dominate. Then pick the mechanism: a reader-writer lock, sharded locks, a bounded work queue with a thread pool, or per-thread data merged later. State the behaviour under overload and the cost of each choice under contention.

Questions & practice

Why is Quicksort preferred for arrays and Merge Sort for linked lists?

Medium · Data Structures and Algorithms

Approach

  1. Arrays: quicksort partitions in place with sequential, cache-friendly scans and small constants. Recursing into the smaller partition and looping on the larger keeps the stack at O(log n). Merge sort on an array needs an O(n) auxiliary buffer plus copying.
  2. Linked lists: there is no random access, so median-of-three or random pivot selection costs a walk, and Hoare-style two-ended partitioning does not fit a singly linked list. Merge sort only relinks nodes, so merging needs no buffer. Bottom-up merge sort uses O(1) extra space, top-down uses O(log n) recursion.
  3. Guarantees: merge sort is O(n log n) in every case and stable. Quicksort averages O(n log n) but degrades to O(n^2) on bad pivots, for example sorted input with a first-element pivot. Randomised or median-of-three pivots make that unlikely, and introsort removes it by switching to heapsort past a depth limit proportional to log n.
  4. Edge cases to name: many duplicate keys (use three-way Dutch-flag partitioning), already-sorted input, and finding a list's midpoint with slow/fast pointers, which costs O(n) per level and leaves the total at O(n log n).

Follow-up

  • Write merge sort for a singly linked list in C, including the split and the merge.
  • How does introsort bound quicksort's worst case, and what does std::sort use in practice?
  • When would you still choose merge sort for an array, for example when you need stability or are sorting data that does not fit in memory?

Print the left view of a binary tree.

Medium · Data Structures and Algorithms

Approach

  1. Define the output first: the first node visible at each depth when the tree is viewed from the left. That node can sit in the right subtree when the left subtree is shallower, so following only left pointers is wrong.
  2. BFS: run a level-order traversal with a queue, read the queue size at the start of each level, and output the first node dequeued at that level. O(n) time and O(w) space, where w is the maximum width.
  3. DFS: do a preorder visit (node, left, right) carrying the depth and a max-depth-seen value, and output a node whenever its depth exceeds that maximum. O(n) time and O(h) stack, which becomes O(n) on a skewed tree.
  4. Edge cases: an empty tree (NULL root), a single node, and a right-only skewed tree, where every node is in the view. In C, size the BFS queue as an array of node pointers with capacity n, or use the recursive DFS.

Follow-up

  • How would you print the right view, and what changes in each version?
  • The tree is deep enough to overflow the recursion stack. How do you make the DFS iterative?
  • How would you print the top view, where nodes are grouped by horizontal distance instead of depth?

Reverse each word in a given string.

Medium · Data Structures and Algorithms

Approach

  1. Clarify the transformation: reverse the characters inside each word but keep the word order ("hello world" becomes "olleh dlrow"). Also ask how multiple, leading and trailing spaces, tabs and punctuation should be treated.
  2. Do it in place with two pointers: scan for the start and end of each word, then swap characters inward from both ends. O(n) time and O(1) extra space on a mutable buffer.
  3. C pitfalls: modifying a string literal is undefined behaviour, so copy it into a char array first. Stop at the NUL terminator, and cast to unsigned char before calling isspace.
  4. Related trick: to reverse the word order instead, reverse the whole string and then reverse each word. Note that byte-wise reversal corrupts multi-byte UTF-8 characters, so Unicode input needs reversal by code point.

Follow-up

  • Reverse the order of the words in place, with O(1) extra space.
  • Collapse runs of spaces to a single space in the same pass.
  • How would you handle UTF-8 input without breaking multi-byte characters?

Find the sum of elements in a matrix.

Medium · Data Structures and Algorithms

Approach

  1. Baseline: a nested loop is O(rows x cols), and that is optimal because every element has to be read once. Say so instead of searching for a faster single-query algorithm.
  2. Correctness: accumulate into a wider type (int64_t or long long) so the sum of many ints cannot overflow. For floating point, mention Kahan or pairwise summation to limit rounding error.
  3. Performance in C: 2D arrays are row-major, so put rows in the outer loop and columns in the inner loop. Column-first traversal strides by the row length and misses cache on large matrices.
  4. Representation and edge cases: a true 2D array, an int** of separately allocated rows, or a flat array indexed as i*cols + j. Handle zero rows or columns, jagged rows and NULL input.
  5. Extension: for many sub-rectangle queries, build an (r+1)x(c+1) prefix-sum table in O(rc). Answer each query in O(1) as P[r2+1][c2+1] - P[r1][c2+1] - P[r2+1][c1] + P[r1][c1].

Follow-up

  • Elements are also updated between queries. What structure keeps both operations fast? (A 2D Fenwick tree gives O(log r x log c) each.)
  • The matrix is mostly zeros. How would you store it and sum it?
  • How would you split the sum across threads without false sharing on the partial sums?

Calculate the distance between two points.

Medium · Data Structures and Algorithms

Approach

  1. Clarify the metric and the space: Euclidean sqrt(dx^2 + dy^2), Manhattan |dx| + |dy|, Chebyshev max(|dx|, |dy|), how many dimensions, and whether the points are latitude/longitude, which needs the haversine great-circle formula.
  2. Numerics in C: hypot(dx, dy) avoids intermediate overflow and underflow. With integer coordinates, compute dx*dx in a 64-bit type, because squaring a large int32 difference overflows.
  3. When only the ordering matters, as in nearest-point checks, compare squared distances and skip sqrt. When comparing floating-point results, use a tolerance instead of ==.
  4. Show where the primitive leads: the k closest points to an origin with a size-k max-heap in O(n log k), and the closest pair among n points, O(n^2) brute force or O(n log n) divide and conquer.

Follow-up

  • Return the k points closest to a given point, and state the complexity.
  • Find the closest pair among n points faster than O(n^2).
  • How does the computation change for points on the Earth's surface?

Discuss memory management strategies in C.

Medium · System Design and Networking Fundamental

Approach

  1. Lay out the storage durations: automatic (stack, freed on return, so returning a pointer to a local is undefined behaviour), static (lives for the whole program), and allocated (malloc, calloc, realloc, free). malloc returns uninitialised memory. calloc zeroes it, and mainstream implementations check the count*size multiplication for overflow.
  2. Ownership discipline: document who frees each allocation, free on every error path (a single cleanup label with goto is common in C), and set pointers to NULL after free. free(NULL) is a no-op. Never write p = realloc(p, n), because the original block leaks if realloc fails; assign to a temporary first.
  3. Name the failure classes and the tools that catch them: leaks, double free, use-after-free, buffer overruns and uninitialised reads. Use AddressSanitizer and LeakSanitizer in tests and Valgrind memcheck when you can't rebuild.
  4. Strategies for hot paths: preallocate at startup; use fixed-size pools or slabs whose free lists give O(1) allocation and free with no fragmentation (a natural fit for packet buffers); use arenas freed all at once per request; use reference counting for buffers shared between stages.

Follow-up

  • Implement a fixed-size block pool allocator in C with alloc and free.
  • Why can a process's memory usage stay high after it frees most of its allocations?
  • How would you make an allocator scale across threads without one global lock?

Discuss the trade-offs of using specific data structures in a real-time system.

Medium · Technical Problem Solving

Approach

  1. Frame the deciding metric: a real-time path needs a bounded worst-case latency, not a good average. Judge every structure by its worst single operation and by whether it allocates.
  2. Hash tables give O(1) average lookups, but a resize is an O(n) pause and collisions can degrade lookups. Pre-size the table or rehash incrementally. Dynamic arrays push in amortised O(1) with occasional O(n) reallocation, whereas a fixed-capacity ring buffer is O(1) on every operation and never allocates.
  3. Balanced trees (red-black, AVL) guarantee O(log n) per operation and keep keys ordered. Tries bound lookup by key length, as in longest-prefix match. Linked lists give O(1) insertion when you already hold the node, but pointer chasing misses cache, so arrays often win in practice.
  4. Timers and scheduling: a binary heap gives O(log n) insertion and removal of the minimum. A timing wheel gives O(1) insertion and cancellation at the cost of coarser resolution. Hierarchical wheels extend the range.
  5. Memory and synchronisation: keep malloc off the hot path by using preallocated pools, and avoid garbage-collected runtimes for strict deadlines. With locks, account for priority inversion, which priority-inheritance mutexes address.

Follow-up

  • Design a structure to manage a million connection timeouts with cheap insert and cancel.
  • How would you make a hash table resize without a long pause?
  • When is a lock-free structure worse than a lock in a real-time system?

Replace offset paging on the resource feed with keyset

Easy · keyset pagination · composite index · sort order

resource holds resource_id, tenant_id, owner_user_id, title, body_ref, version, status ('draft','active','archived','deleted'), created_at, updated_at, deleted_at, with an index on (tenant_id, status, updated_at DESC, resource_id DESC). The listing endpoint returns active resources for one tenant, newest update first, 50 per page, today with LIMIT 50 OFFSET n. Tenants reach page 400 and rows are created while they read. Write the keyset query, define what the cursor carries and how it is encoded, and say which part of the index each predicate uses. Assume PostgreSQL 16.

Approach

  1. Name the two failures separately. OFFSET 20000 makes the server produce and discard 20,000 rows, so page cost grows with depth rather than with page size. Independently, any write that changes how many rows sort above the offset moves the window between two fetches, and the direction decides which anomaly you get: an insert lands at the head of updated_at DESC and pushes already-returned rows down past the boundary, so they are returned a second time; a delete above the offset, or a row whose updated_at is bumped above the cursor, pulls rows up and one is never returned at all. Nothing in the response reveals either.
  2. Write the seek: WHERE tenant_id = $1 AND status = 'active' AND (updated_at, resource_id) < ($2, $3) ORDER BY updated_at DESC, resource_id DESC LIMIT 50. The row-value comparison is one index range rather than a disjunction, and both columns are NOT NULL, which is what makes that comparison well defined.
  3. Map each predicate onto the index: tenant_id and status are equality on the leading columns, (updated_at, resource_id) is the range, and the ORDER BY matches the index order so no Sort node appears and the scan stops after 50 rows. The DESC in the definition only matters for mixed directions — a plain ascending btree on the same columns is read backwards for this query.
  4. Put both sort columns in the cursor and nothing the client can tamper with into another tenant: base64 of (updated_at, resource_id), validated server-side, with tenant_id taken from the principal.

Follow-up

  • The client asks for 'jump to page 400'. What do you offer instead, and what does the honest version cost?
  • Sort order becomes user-selectable across four columns. How many indexes is that, and which would you refuse to add?

Write the update path that detects a concurrent edit

Medium · optimistic concurrency · transactional outbox · audit history

resource carries version INT NOT NULL DEFAULT 1. resource_revision holds revision_id, resource_id, version, actor_user_id, change_kind, patch JSONB, request_id, created_at with UNIQUE (resource_id, version). outbox_event holds aggregate_type, aggregate_id, aggregate_version, event_type, payload, status. A PUT carries the version the client read. Write the exact statements for the single transaction that applies the edit, records the revision and enqueues 'resource.updated', and give the handler's branch on zero affected rows. Then say what PostgreSQL 16 does under READ COMMITTED when two of these updates hit one row at once.

Approach

  1. One transaction, three writes, no network call inside it: UPDATE resource SET title = $3, version = version + 1, updated_at = now() WHERE resource_id = $1 AND tenant_id = $4 AND version = $2; then INSERT the resource_revision row at version $2 + 1; then INSERT the outbox_event row at the same aggregate_version. The event goes to a table rather than a broker because no transaction spans both.
  2. Branch on the affected-row count before doing anything else. Zero has three causes — stale version, wrong tenant, row gone — so re-read once and map to 409 carrying the current version, or 404 for an id outside the caller's tenant, which also stops the endpoint confirming that another tenant's id exists.
  3. State the engine behaviour instead of assuming it. Under READ COMMITTED the second UPDATE blocks on the row lock, and when the first commits PostgreSQL re-evaluates the WHERE clause against the newly committed row, so the version predicate now fails and the statement reports zero rows. Under REPEATABLE READ the identical collision raises SQLSTATE 40001 instead, so the handler must fold both shapes into one conflict response.
  4. Keep UNIQUE (resource_id, version) even though the predicate already serialises writers. It is what makes a lost update unwritable if any other path ever reaches the revision table, and it converts a logic bug into 23505 rather than into a silently missing history row.

Follow-up

  • A client sends the version it read ten minutes ago and the resource has moved three versions. What is in your 409 so it can resolve the conflict without a full re-fetch?
  • Two editors, two disjoint fields, no overlap. Does your answer still refuse the second write, and should it?

What happens after you put a URL into a browser?

Medium · System Design and Networking Fundamental

Approach

  1. Parse and check local state: split the URL into scheme, host, port and path; check HSTS (to force HTTPS) and the browser's HTTP cache, which can answer without touching the network.
  2. Name resolution: browser and OS caches and the hosts file, then the stub resolver asks a recursive resolver, which walks root, TLD and authoritative servers for an A or AAAA record. Answers are cached per their TTL. DNS uses UDP port 53 and falls back to TCP when a response is truncated.
  3. Getting packets out: ARP (IPv4) or neighbour discovery (IPv6) finds the default gateway's MAC. NAT at the home router rewrites the source address and port. Each router forwards by longest-prefix match on the destination IP, decrements TTL and rewrites the link-layer header.
  4. Connection and request: TCP three-way handshake (SYN, SYN-ACK, ACK), then a TLS 1.3 handshake (one round trip, certificate chain validation), then an HTTP GET over HTTP/1.1 or HTTP/2. HTTP/3 runs over QUIC on UDP instead. Handle redirects (301/302) and cache headers.
  5. Rendering: parse HTML into the DOM and CSS into the CSSOM, run scripts, then layout and paint, fetching subresources over the reused connection. For a networking role, give the most depth to the DNS, routing and TCP steps.

Follow-up

  • How does a router choose the next hop, and where do its routing table entries come from?
  • What changes when the site is served over HTTP/3?
  • DNS resolution fails or returns a stale address. How would you diagnose which step broke?

Explain the interaction between network layers.

Medium · System Design and Networking Fundamental

Approach

  1. Pick a model and map real protocols onto it: OSI's seven layers or the TCP/IP stack. Application (HTTP, DNS), transport (TCP, UDP, identified by ports), network (IP, routing), link (Ethernet frames, MAC addresses), physical.
  2. Encapsulation going down: each layer prepends its header to the payload from the layer above. Demultiplexing coming up uses fields in each header: EtherType (0x0800 IPv4, 0x86DD IPv6), the IP protocol number (6 TCP, 17 UDP), then the port that selects a socket.
  3. Glue between layers: ARP maps an IPv4 next-hop address to a MAC. The link MTU drives TCP's MSS, and IPv4 fragmentation or Path MTU Discovery relies on ICMP 'fragmentation needed' (IPv4) or 'Packet Too Big' (IPv6).
  4. Hop behaviour: a switch forwards on destination MAC using its learned MAC table. A router strips and rebuilds the L2 header at every hop and decrements TTL (or hop limit), and for IPv4 it also recomputes the header checksum. The IP addresses stay end to end unless NAT rewrites them.
  5. Name where layering is crossed: TCP's checksum covers a pseudo-header with the IP addresses, NAT rewrites L4 ports, and an 802.1Q VLAN tag sits between the source MAC and the EtherType.

Follow-up

  • Walk one packet from a laptop through a switch and two routers. What changes in each header at each hop?
  • What is the difference between a switch and a router, and where does a VLAN fit?
  • What happens when a packet is larger than the MTU of a link on its path?

How do you optimize software for high-throughput network environments?

Medium · System Design and Networking Fundamental

Approach

  1. Define the target and measure before changing code: packets per second versus bits per second (small packets stress per-packet cost), latency percentiles, and where the cycles go (perf, cache-miss and syscall counts, lock contention).
  2. Cut per-packet overhead: batch I/O (recvmmsg/sendmmsg on Linux, polling instead of an interrupt per packet), avoid copies with shared ring buffers, and avoid malloc per packet by using preallocated buffer pools. Kernel-bypass or fast-path options include DPDK poll-mode drivers and AF_XDP/XDP.
  3. Make memory access cheap: keep hot structures cache-line aligned, separate per-core data to avoid false sharing, prefetch the next packet's header, use hugepages to cut TLB misses, and keep buffers and NIC queues on the same NUMA node.
  4. Scale across cores: NIC receive-side scaling hashes each flow to one queue and core, which keeps packets in order. Process each packet to completion on its core, pass work between stages through single-producer/single-consumer lock-free rings, and keep per-core counters that are summed on read.
  5. State the trade-offs: busy polling buys latency at the cost of a fully used core, larger batches raise throughput but add latency, and lookup structures (a hash table for exact match, a trie for longest-prefix match) must have a bounded worst case.

Follow-up

  • Profiling shows most time in cache misses on a lookup table. What do you change?
  • How would you implement a fast longest-prefix-match lookup for a route table?
  • Throughput is fine but tail latency spikes. Where do you look?

Design an algorithm to handle concurrent data requests efficiently.

Medium · Technical Problem Solving

Approach

  1. Clarify the workload before choosing a design: the read/write ratio, whether requests share mutable state, ordering requirements, the latency target, and what should happen under overload.
  2. Execution model: a fixed worker pool fed by a bounded queue instead of a thread per request. The bound gives backpressure: when the queue is full, reject the request or make the caller wait.
  3. Protect shared data in proportion to contention: one mutex is simplest. A reader-writer lock suits read-heavy access. Lock striping (shard the map by key hash, one lock per shard) removes most contention. Atomics or compare-and-swap suit counters, and RCU or copy-on-write snapshots let readers proceed without locks.
  4. Remove duplicate work: coalesce concurrent requests for the same key into one backend call whose result all waiters share (single-flight), and cache the results with explicit invalidation.
  5. Correctness: acquire locks in a fixed global order to avoid deadlock, never hold a lock across I/O, and watch for writer starvation under reader-writer locks. Measure contention before reaching for lock-free structures.

Follow-up

  • How do you stop writers from starving under a reader-writer lock?
  • What is the ABA problem in a lock-free stack or queue, and how is it avoided?
  • The queue is full. What should the system return to callers, and why?

How would you approach debugging a memory leak in a production system?

Medium · Technical Problem Solving

Approach

  1. Confirm it is a leak: plot RSS or heap size against time and load. Rule out bounded caches still warming, unbounded queues or backlogs, and allocator fragmentation, where freed memory is not returned to the OS. Check whether growth began with a deploy, a config change or a traffic pattern.
  2. Find the allocation site: in production, use a low-overhead heap profiler (jemalloc or tcmalloc profiling, heaptrack) or eBPF tracing of outstanding allocations by call stack (the bcc memleak tool). In a reproduction, use Valgrind memcheck or AddressSanitizer with LeakSanitizer.
  3. Diff two heap snapshots taken some time apart. The call stack whose outstanding bytes keep growing is the suspect. Then read its error paths for a missed free, a reference count that is never decremented, or a map or list that is never evicted.
  4. Contain the damage while investigating: alert on memory growth, set memory limits, and restart instances on a rolling basis. Land the fix with a regression test that runs the triggering path under LeakSanitizer in CI.

Follow-up

  • The process freed the memory but its RSS did not drop. Why might that be?
  • The leak only appears under one traffic pattern you cannot reproduce locally. What do you do?
  • How do you investigate a long-running daemon that you cannot restart or attach a heavy profiler to?

Suggested practice checklist

The plan begins with the reported coding questions in C or C++, moves to memory and networking fundamentals, and finishes with throughput, debugging, concurrency and a full spoken rehearsal.

Day 1 — Sorting trade-offs in C

  • Write Quicksort for an int array in C with a random or median-of-three pivot and Hoare or Lomuto partitioning, and test it on sorted, reversed and all-equal inputs to see the worst case.
  • Write Merge Sort for a singly linked list, using slow and fast pointers to split and relinking nodes to merge, and note the recursion depth and extra space.
  • Write a half-page answer for why Quicksort is preferred for arrays and Merge Sort for linked lists, covering random access, cache locality, stability, worst case and memory.

Deliverable: Two working C programs (array Quicksort, linked-list Merge Sort) and a written comparison answering the sorting question.

Day 2 — Trees and strings

  • Implement the left view of a binary tree with a level-order queue, then with a recursive walk that tracks the deepest level printed so far; run both on a skewed tree and a complete tree.
  • Reverse each word of a string in place on a char array without library calls, then check an empty string, one word, leading and trailing spaces and repeated spaces.
  • State the time and extra-space cost of each solution, and what changes if the input is a read-only string literal.

Deliverable: Working code for the left view (two versions) and for in-place word reversal, with a test list of edge cases for each.

Day 3 — C memory management, matrices and distance

  • Write notes comparing stack, heap, static and memory-pool allocation in C, and list the failure modes of each: leaks, double free, use after free, dangling pointers, fragmentation, buffer overruns.
  • Allocate a matrix as one contiguous block and as an array of row pointers, sum it in row-major order, and explain the cache effect and the freeing of each layout.
  • Write the point-distance function, decide between sqrt and comparing squared distances, and handle integer coordinates that overflow when squared.

Deliverable: A memory-management cheat sheet, a matrix-sum program in two layouts, and a distance function with its overflow cases noted.

Day 4 — Network layers and a packet's path

  • Write the sequence for typing a URL: DNS resolution and caching, TCP three-way handshake, TLS handshake, HTTP request and response, connection close.
  • Draw the encapsulation of one request: application data, TCP segment, IP packet, Ethernet frame, and mark the addresses and ports used at each layer.
  • Explain how a host picks the next hop: its own subnet versus the default gateway, ARP, and longest-prefix match in a routing table, then how the operating system hands received data to a socket and a process.

Deliverable: One annotated diagram of a request's path through the stack, plus a spoken walkthrough of it you can give without looking at the diagram.

Day 5 — Throughput and real-time data structures

  • List the levers for a high-throughput network path: batching, avoiding copies, preallocated buffers in place of per-packet allocation, a lock-free single-producer ring buffer, per-core sharding, and measuring first with a profiler.
  • Build a table of array, linked list, hash table, balanced tree, heap and ring buffer with worst-case cost per operation, allocation behaviour and cache behaviour.
  • Explain which structure you would use for a fixed-size packet queue and for a lookup table that must never pause for a rehash, and why.

Deliverable: A throughput checklist and a data structure trade-off table with a chosen structure and reason for two scenarios.

Day 6 — Memory leaks and concurrent requests

  • Write a short C program with a deliberate leak, find it with AddressSanitizer or Valgrind, and note what each tool does and costs.
  • Write a production leak runbook: confirm growth from resident memory, tie it to load, separate leak from cache or fragmentation, take allocation snapshots, bisect by build, verify the fix.
  • Implement a bounded queue with a mutex and condition variables in C or C++, run it with a pool of worker threads, and decide what happens when the queue is full.

Deliverable: A leak runbook and a working bounded queue with worker threads, with the overload behaviour written down.

Day 7 — Full spoken rehearsal

  • Work through one coding problem and then three spoken answers back to back (sorting trade-offs, the URL walkthrough, the memory leak), with no editor or notes, and record yourself.
  • Replay the recording and mark any answer that stated no assumption, no complexity or no trade-off, then redo those answers.

Deliverable: A recording of the rehearsal, a list of answers to redo, and one rehearsed debugging story.

Explaining your own systems work

Candidates report two technical rounds and no separate behavioral stage. Questions like the memory-leak and real-time trade-off ones are still easiest to answer with a real experience behind them, and the role includes code review, module ownership and system-level debugging. Rehearse a few stories that show concrete evidence, such as what you measured, what you changed and what the result was.

  1. Tell me about a memory, pointer or concurrency bug in C or C++ code that you tracked down. How did you find it, and how did you confirm the fix?
  2. Describe a time you made code measurably faster. What did you measure before and after, and what did you decide not to optimise?
  3. Walk me through a decision where you chose one data structure over another. What did the alternative cost you?
  4. Tell me about a module you owned from design to deployment. Which decision would you make differently now?
  5. Describe a code review where you found a problem with error handling or memory use. How did you raise it?

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?

Narrate an outage you owned from page to postmortem

Hard · incident response · blast radius · postmortems

Pick an incident you personally drove, ideally one where writes were affected rather than reads. In six to eight minutes: state the symptom as it first appeared on a dashboard, the blast radius you established before you knew the cause, the mitigation you applied and when, the mechanism you eventually proved, and the follow-up that would prevent a repeat. Bring numbers: error rate, tenants affected, minutes to mitigate, minutes to resolve. If you cannot name what you measured, choose a different incident.

Approach

  1. Open on the signal rather than the cause: which metric at which percentile moved, on which service, at what time, so the listener follows the same evidence you had rather than a conclusion you already reached.
  2. Separate mitigation from diagnosis out loud. State what you did to stop the bleeding (flag off, shed traffic, drain a lease, roll back a deploy) and say plainly that you did it before the mechanism was known, because those are two jobs with different deadlines.
  3. Establish blast radius in countable terms: how many tenants, how many writes, and crucially whether the effect was loss or only delay. An append-only revision table or a pending outbox row means the change survived and the projection was merely behind, which is a repair rather than a data-loss incident.
  4. Prove the mechanism instead of asserting it. Name the trace span that grew, the plan that flipped to a sequential scan, the lease that expired, plus one alternative you ruled out and the signal that stayed flat while you ruled it out.

Follow-up

  • What would you do differently in the first five minutes, given the same dashboard and no more information?
  • Which follow-up action did you deliberately not take, and why was dropping it the right call?