Interview guides · Arista Networks
Arista Networks Software Engineer Interview Guide
Arista Networks Software Engineer interview guide: reported rounds, C/C++ memory, DSA, GDB debugging and networking questions, plus a 7-day plan.
The 60-second brief
Arista Networks builds networking software and equipment for cloud datacenters, enterprise networks and financial trading environments. Candidates name EOS (Extensible Operating System), the company's network operating system, and CloudVision as the platforms engineers work on. The Software Engineer role is systems work: the languages listed are C and C++, with Python or Go on some teams for control-plane management and automation. Reported responsibilities include implementing features in network daemons, writing unit tests, tuning packet-routing paths, reviewing peers' code, and working with hardware, system test and technical support teams. Reported team areas include EOS, CloudVision and Hardware Interfaces.
This guide is for candidates applying to Arista Networks as a Software Engineer, from entry-level SDE-1 or new-grad openings up to senior roles, which candidates describe as sharing the same fundamentals. It covers the four reported rounds and 18 reported questions in four groups: five data structure and algorithm questions, five low-level C/C++ questions, four debugging questions, and four design and networking questions. It ends with a seven-day plan that assumes you will write, compile and debug code in a plain terminal.
Candidates report four stages over about 3-5 weeks: resume screening, a recruiter conversation, a technical screening (an online assessment or a live interview), and a full technical evaluation with live coding, debugging and system design.
Focus areas
- Data Structures & Algorithms (DSA)
- C++
- String Manipulation
Overview
The reported questions describe systems programming more than application development. Candidates list C and C++ as the working languages, Linux as the environment, and operating systems, computer architecture and networking as the supporting subjects. Many technical rounds are reported to run in a remote terminal over SSH with Vim or Nano, GCC or G++ and GDB, so the first preparation task is practical: be able to write, compile, run and debug a small program with no IDE help.
The 18 reported questions fall into four groups. The data structure group is string compression, the three binary tree traversals with a DFS versus BFS comparison, a root-to-leaf path-sum check, the first missing positive number, and a hand-built LRU cache or generic stack. The low-level group covers struct packing and padding in C, pass-by-value versus pass-by-reference with pointer arithmetic and dangling pointers in C++, vtables and virtual dispatch cost, compiled versus interpreted execution, and bitwise toggling or bit counting. The debugging group covers a mutex deadlock, a C segmentation fault, a C++ double free or leak, and GDB on a core dump or live process. The last group mixes the HTTPS handshake, a time-based key-value store, the OSI model with packet encapsulation and switch fabrics, and an object-oriented design for a network interface monitor or device management system, so it is not purely a system design group.
Candidates also report that emphasis shifts by team: EOS and Hardware Interfaces lean toward C/C++ internals, while CloudVision work leans toward distributed systems design. Spend most of your time on the first three groups, because they appear in coding and debugging sessions, then keep a short, accurate networking and design repertoire ready. For every structure you write, say its time complexity, its extra memory, and whether it lives on the stack or the heap. Leadership-style conversations are reported to centre on your past technical projects, so know your own resume at code level.
The Arista Networks interview process
Candidates describe four stages over roughly 3-5 weeks. The round names below are the ones candidates report, not a published process.
Resume Screening
Candidates describe the first stage as a review of your resume against the role. There is nothing to rehearse aloud here, so the work is on the page. The requirements candidates list for this role are C or C++ with pointers and memory structures, data structures, algorithms, operating systems, computer architecture, Linux terminal experience and networking basics such as TCP/IP and routing. GDB, Valgrind, AddressSanitizer, multi-threading, Python or Bash scripting and exposure to device drivers or PCIe are listed as nice to have. Make each of those that you truly have visible in one line of a bullet, and be ready to explain any line at code level, because later conversations are reported to go through past projects.
What to demonstrate
- Match between your experience and the listed requirements: C or C++, data structures, operating systems, Linux and networking
- Visible hands-on evidence for the nice-to-have areas, such as debugging tools, concurrency or scripting
How to prepare
- Rewrite each systems project bullet as language, environment, constraint and result, for example a C daemon on Linux where you removed a memory leak found with Valgrind.
- Add the tools you have actually used, such as GDB, Valgrind or AddressSanitizer, and cut skills you could not discuss for two minutes.
- Choose the level you are applying for, from SDE-1 or new grad to senior, and make the resume's scope match it.
Recruiter Conversation
Candidates describe a conversation with a recruiter or an engineering coordinator about the role. No coding is reported for this stage. Use it to learn what the technical stages will look like for your team and level, and to give a short, specific account of your background. Reported team areas include EOS, CloudVision and Hardware Interfaces, and candidates say the mix of low-level C/C++ and distributed systems design shifts between them, so a precise answer about which kind of work you want is more useful than a general one.
What to demonstrate
- Fit between your background and the role as the recruiter or coordinator describes it
- Your interest in a specific kind of systems work, such as operating system components, cloud monitoring tools or switch driver interfaces
How to prepare
- Write a two-minute summary of your strongest C or C++ project, stressing what you owned and which constraint (memory, concurrency, latency) shaped it.
- Prepare one honest sentence on why networking software interests you that refers to the work itself, such as protocol handling, packet paths or device state.
- Ask whether the technical screening is an online assessment or a live session, which languages and editor it uses, and whether it runs over SSH, so you can rehearse in the same setting.
Technical Screening
Candidates report either an online coding assessment or a live technical interview focused on core data structures and algorithms. The reported topic areas are strings, linked lists, trees, heaps and bit manipulation, and candidates are expected to build structures themselves rather than lean on library helpers. The data structure questions in this guide are the practice material for this stage: string compression, tree traversals and path-sum, the first missing positive number, a hand-built LRU cache or stack, and bit operations. Write each in C or C++, compile it, and test the edge cases yourself.
What to demonstrate
- Working code for core data structure and algorithm problems
- Time and space complexity analysis stated alongside the code
- Edge-case handling, such as empty input, a single element or a head-pointer change
How to prepare
- Implement the string compression problem in place with a character array, and decide what happens to a run of length one and how many bytes the output needs.
- Write all three tree traversals recursively and with an explicit stack, add a queue-based BFS, and write the root-to-leaf path-sum check with the leaf condition tested explicitly.
- Build an LRU cache from a hash map plus a doubly linked list, and a linked-list node removal with a Node** parameter that frees each removed node.
- Solve the first missing positive number in O(n) time and O(1) extra space, and say how the answer changes when the array is sorted.
Full Technical Evaluation
Candidates describe intensive rounds with senior engineers that combine live coding, debugging and system design. Many of these sessions are reported to run in a remote Linux terminal over SSH, using Vim or Nano, GCC or G++ and GDB, though some use web-based shared code editors. Managerial and leadership conversations are reported to go through past technical projects, design concepts and team alignment. The reported questions in this guide that fit this stage's formats are the low-level C/C++ questions (struct padding, references and pointers, vtables, bit operations), the four debugging questions, and the networking and design questions: the HTTPS handshake, a time-based key-value store, the OSI model and an object-oriented network monitor module.
What to demonstrate
- Live coding that compiles and runs in a terminal environment
- Debugging of unfamiliar C or C++ code: deadlocks, crashes, double frees and leaks
- Reasoning about system design and networking fundamentals
- Discussion of your own past technical projects and how you work with a team
How to prepare
- Compile with gcc -g -Wall -Wextra and -fsanitize=address,undefined, and run Valgrind on the same binaries, so each debugging tool is familiar.
- Rehearse a fixed GDB sequence: run, bt, frame N, print and x/ on suspect pointers, info threads, thread apply all bt, and gdb ./prog core for a dump.
- Prepare a spoken answer for each of struct padding, the vtable layout, the HTTPS handshake and packet encapsulation, ending each with a concrete example.
- Sketch two designs on paper: a time-based key-value store, and a class model for a network interface monitor with its failure cases.
Where candidates lose points
Needing an IDE to write or fix code when the session is a terminal over SSH
Run at least two full practice sessions in Vim or Nano with gcc or g++ only. Have the compile line, a Makefile-free build command and the gdb commands in muscle memory before the loop.
Guessing fixes on the deadlock, segfault or double-free questions instead of isolating the cause
State what the code does, form one hypothesis, and test it with evidence: backtraces from every thread in GDB, AddressSanitizer's report, or a Valgrind trace. For two mutexes taken in opposite order, name a single global lock order or use std::scoped_lock, then show why the cycle is gone.
Breaking linked-list and in-place string code on empty input, single nodes or buffer growth
Before coding the Node** removal, list the empty list, removal of the head, removal of every node and a missing target. For in-place compression, ask whether a run of one writes a count, because 'a' becoming 'a1' grows the output and affects whether O(1) space is possible.
Explaining struct padding, vtables or pass-by-reference in abstract terms with no concrete layout
Use numbers. A struct with a char, an int and a char occupies 12 bytes on a typical ABI with 4-byte ints, and reordering the int first makes it 8. Show that a class with a virtual function adds a hidden pointer, and that a virtual call is an indirect load through it.
Answering the networking and design questions with a list of layer names or buzzwords
Follow one packet or one request end to end. For the HTTPS handshake, give the order: TCP connection, hello messages, certificate check, key exchange, session keys, encrypted application data. For the key-value store, name the data structure per key, the lock strategy and the read path.
Questions & practice
Write an algorithm to collapse consecutive identical lowercase characters into the character followed by its c
Medium · Data Structures & Algorithms
Write an algorithm to collapse consecutive identical lowercase characters into the character followed by its count (e.g., converting "aaabbbccc" into "a3b3c3").
Approach
- Settle the format first: is a single character written as "a1" or just "a", and can a run reach 10 or more? Both change the output length and the code.
- Scan once with two indices: i marks the start of a run, j moves forward while s[j] == s[i]. Write s[i], then the decimal digits of (j - i), then set i = j. That is O(n) time.
- In C, size the output for the worst case. If every count is written, "abc" becomes "a1b1c1", so you need 2n + 1 bytes. Write counts with snprintf, or emit the digits by modulo into a small buffer and reverse them, so multi-digit runs work.
- Doing it in place with O(1) extra space only works when single characters carry no count. Then the write index can never pass the read index. If every count is written, the output can be longer than the input, so say this before you promise in-place.
- Edge cases to test aloud: empty string, one character, a run of exactly 10 (two digits), the whole string as one run, and input that is not lowercase.
Follow-up
- Write the decoder, and say how it tells a digit that belongs to a count from one that is data.
- Change it to return the original string whenever the encoded form would be longer.
- Do it in place on a char array, with O(1) extra space, for the variant that drops the count of 1.
Given a target value, construct a full binary tree and write a function to determine if a matching path-sum ex
Medium · Data Structures & Algorithms
Given a target value, construct a full binary tree and write a function to determine if a matching path-sum exists along any root-to-leaf path.
Approach
- Construction: build the tree from a level-order array. The children of index i sit at 2i + 1 and 2i + 2, with a sentinel for a missing node. Free it afterwards with a postorder walk.
- Recursive check: subtract the node's value from the target and recurse. At a leaf (both children NULL), return whether the remainder is 0. Combine the two children with ||, which also short-circuits once a path is found.
- Complexity: O(n) time, because every node is visited at most once, and O(h) stack. The iterative form pushes (node, remaining) pairs on an explicit stack.
- Edge cases: an empty tree returns false even for target 0. A node with one child is not a leaf. Negative values mean you cannot prune when the running sum passes the target. Accumulate in long long if the values can overflow int.
Follow-up
- Return every root-to-leaf path that matches, using backtracking with a shared path buffer.
- Count the downward paths that start at any node and sum to the target, using a prefix-sum hash map in O(n).
- Find the maximum path sum between any two nodes.
Implement an algorithm to find the first missing positive number in a contiguous or sorted integer array using
Medium · Data Structures & Algorithms
Implement an algorithm to find the first missing positive number in a contiguous or sorted integer array using optimal time and space complexity.
Approach
- Clarify the input first: sorted or unsorted, duplicates allowed, negatives allowed, and whether the array may be modified. The best answer depends on all four.
- Sorted input: keep expected = 1, skip values below 1 and repeats, increment expected when a[i] == expected, and stop when a[i] > expected. That is O(n) time and O(1) space. If the values are distinct positives, the predicate a[i] == i + 1 is monotone, so binary search gives O(log n).
- Unsorted, O(n) time and O(1) space: the answer is in [1, n + 1]. Cyclic placement: while 1 <= a[i] <= n and a[a[i] - 1] != a[i], swap a[i] into slot a[i] - 1. Then the first i with a[i] != i + 1 gives i + 1; if there is none, the answer is n + 1.
- Explain why it is linear: every swap puts one value in its final slot, so there are at most n swaps in total. The a[a[i] - 1] != a[i] check is what stops duplicates from looping forever.
- Name the alternatives and their costs: a hash set takes O(n) extra space, sorting takes O(n log n). Check bounds before computing a[i] - 1 so INT_MIN cannot overflow.
Follow-up
- Solve it without modifying the input. What does that cost in space?
- A sorted array of consecutive integers with exactly one missing: find it in O(log n).
- The numbers arrive as a stream too large for memory. What changes?
Implement a custom Least Recently Used (LRU) cache or generic stack data structure from scratch, detailing how
Medium · Data Structures & Algorithms
Implement a custom Least Recently Used (LRU) cache or generic stack data structure from scratch, detailing how you handle element life-cycles and memory overhead.
Approach
- For the LRU cache, use a hash map from key to node plus a doubly linked list with sentinel head and tail nodes. get looks the key up, unlinks the node and moves it to the front. put updates or inserts, and on overflow evicts tail->prev. Every operation is O(1).
- In C++, use std::list<std::pair<K, V>> with std::unordered_map<K, std::list<...>::iterator>, and move entries to the front with splice. splice moves nodes without copying and keeps their iterators valid.
- Lifecycle: the list owns the nodes and the map holds non-owning references. On eviction, erase the map entry before you destroy the node, so nothing dangles. The cost per entry is two link pointers, the key and value, and the hash-bucket overhead.
- For the generic stack, use a template over T backed by a growable array that doubles its capacity, giving amortized O(1) push. Decide what pop does when the stack is empty (return a bool or optional, or throw), and destroy elements on pop so types with destructors release their resources.
- Edge cases: capacity 0, putting a key that already exists (update and promote it; do not insert twice), and getting a missing key. Also note that get changes recency, so under threads it is a write.
Follow-up
- Make the cache thread-safe. When does a single mutex stop being enough, and would sharding the cache help?
- Add per-entry expiry (TTL) and say when expired entries get reclaimed.
- Change the eviction policy to LFU while keeping O(1) operations.
Explain struct packing, memory alignment, and how padding affects memory layout and cache alignment in C.
Medium · Systems Programming & Low-Level C/C++
Approach
- Each type has an alignment requirement. On x86-64 System V that is 1 for char, 2 for short, 4 for int and 8 for double and pointers. The compiler pads so every member's offset is a multiple of its alignment, and pads the end so sizeof is a multiple of the largest member alignment, which keeps array elements aligned.
- Work an example: struct { char a; int b; char c; } has offsets 0, 4 and 8 and sizeof 12. Reordered as { int b; char a; char c; } it has sizeof 8. Check it with offsetof and a _Static_assert on sizeof.
- Packing with attribute((packed)) or #pragma pack(1) removes the padding (the example becomes 6 bytes). The cost is unaligned access: slower on x86, a possible fault on strict-alignment CPUs, and a warning from GCC if you take a plain pointer to a packed member.
- For wire formats, a portable way to read a header field from a raw byte buffer is memcpy into an aligned local followed by ntohs or ntohl. Casting buf + offset to uint32_t * is undefined behaviour.
- Cache effects: lines are commonly 64 bytes. Put hot fields together, and give counters written by different threads separate lines (alignas(64) or _Alignas) to avoid false sharing.
Follow-up
- Why does sizeof include trailing padding even though no member follows it?
- Two threads increment adjacent counters and it scales badly. What is happening, and how do you fix it?
- Parse an Ethernet plus IPv4 header out of a byte buffer without undefined behaviour.
Demonstrate how pass-by-value differs from pass-by-reference in C++, and explain pointer arithmetic, dangling
Medium · Systems Programming & Low-Level C/C++
Demonstrate how pass-by-value differs from pass-by-reference in C++, and explain pointer arithmetic, dangling pointers, and dynamic memory management via explicit allocations.
Approach
- Show it with swap: void swap(int a, int b) changes only its copies, while void swap(int &a, int &b) changes the caller's variables. For class types, pass-by-value runs the copy (or move) constructor. Use const T& for large read-only arguments to avoid that copy.
- Passing a pointer is pass-by-value of an address: it can be null and can be reseated. A reference must be bound when it is created and cannot be reseated afterwards.
- Pointer arithmetic is scaled by the element size: p + 1 advances sizeof(*p) bytes. Subtracting two pointers into the same array gives a ptrdiff_t count of elements. Forming a pointer beyond one-past-the-end, or comparing pointers from different arrays, is undefined.
- Dangling pointers come from returning the address of a local, using memory after delete or free, and keeping a vector iterator or pointer across a reallocation. Setting a pointer to nullptr after delete protects only that one copy.
- new pairs with delete and new[] with delete[]. malloc and free do not run constructors or destructors, and mixing the families is undefined. Prefer RAII owners (std::unique_ptr, std::vector), so the cleanup cannot be skipped by an early return or an exception.
Follow-up
- When is taking a parameter by value and moving from it better than const T&?
- What does sizeof give for an array parameter inside a function, and why?
- Where does an rvalue reference fit alongside these three ways of passing an argument?
What are virtual functions in C++, how is a virtual method table (vtable) implemented by the compiler, and wha
Medium · Systems Programming & Low-Level C/C++
What are virtual functions in C++, how is a virtual method table (vtable) implemented by the compiler, and what is the runtime overhead of virtual dispatch?
Approach
- A virtual function is resolved at run time from the object's dynamic type when you call it through a base pointer or reference. A pure virtual function (= 0) makes the class abstract.
- Common implementation under the Itanium ABI (MSVC is similar): each polymorphic class has one static vtable of function pointers. Itanium also stores offset-to-top and RTTI entries. Each object carries a hidden vptr, usually at offset 0, which the constructors set.
- A virtual call loads the vptr, loads the slot, then makes an indirect call. The costs are an extra pointer per object (plus any padding it causes), two dependent loads, a possible indirect-branch mispredict, and lost inlining unless the compiler can devirtualize (final, a known dynamic type, LTO).
- While a base constructor or destructor runs, the vptr points at the base vtable, so a virtual call there does not reach the derived override. Deleting a derived object through a base pointer needs a virtual destructor; without one the behaviour is undefined.
- With multiple inheritance an object can hold several vptrs, and calls through a non-primary base go through thunks that adjust this.
Follow-up
- What happens if a constructor calls a virtual function?
- Compare sizeof for a class before and after you add one virtual method.
- How would CRTP give you static polymorphism here, and what do you give up?
Describe the difference between process compilation and execution models in compiled languages versus interpre
Medium · Systems Programming & Low-Level C/C++
Describe the difference between process compilation and execution models in compiled languages versus interpreted runtime environments.
Approach
- Walk through the C/C++ pipeline: the preprocessor (gcc -E) expands includes and macros, the compiler (-S) emits assembly, the assembler (-c) produces object files with unresolved symbols, and the linker resolves them against other objects and static or shared libraries to produce an ELF executable.
- At run time, execve has the kernel map the ELF segments. For dynamic executables, the dynamic linker (ld.so) loads shared libraries and resolves symbols through the GOT and PLT. Control then passes from _start through the C runtime to main.
- The process image has text, data, bss, heap, memory-mapped libraries and stack. Type errors are caught at compile time, while undefined references are link-time errors.
- Interpreted runtimes work differently. CPython compiles source to bytecode and runs it in an interpreter loop, checking types as each operation runs. JIT runtimes such as the JVM's HotSpot or V8 compile hot code to native code at run time using profiling data.
- The trade-offs: startup time, peak throughput, portability of the artifact, deployment (one binary or a runtime plus source), and when errors surface.
Follow-up
- Static or dynamic linking: what does each cost in size, updates and startup?
- Why is 'undefined reference' a linker error and not a compiler error?
- What happens between execve and the first line of main?
Implement a function using low-level bitwise operators to toggle specific bit positions or count set bits in a
Medium · Systems Programming & Low-Level C/C++
Implement a function using low-level bitwise operators to toggle specific bit positions or count set bits in a integer variable.
Approach
- Basic operations on bit k: set it with x |= 1u << k, clear it with x &= ~(1u << k), toggle it with x ^= 1u << k, and test it with (x >> k) & 1u. Use unsigned operands; 1 << 31 on a 32-bit int is undefined behaviour.
- Shifting by an amount equal to or larger than the type's width is undefined, so check k. To toggle a field of len bits starting at bit start, use the mask ((1u << len) - 1) << start, and handle len == 32 separately.
- Counting set bits: the naive loop runs once per bit of the word. Kernighan's loop x &= x - 1 clears the lowest set bit, so it runs once per set bit. A byte lookup table or the SWAR parallel sum also work.
- Name the builtins: __builtin_popcount and C++20 std::popcount compile to POPCNT when the target supports it.
- Do the shift loop on unsigned. Right-shifting a negative signed value is implementation-defined, and in practice it is arithmetic, so the loop never reaches 0.
Follow-up
- Check whether x is a power of two in one expression.
- Return the popcount of every integer from 0 to n in O(n).
- Reverse the bits of a 32-bit unsigned integer.
Analyze a multi-threaded code snippet exhibiting thread locking issues, identify the deadlock condition, and r
Medium · Debugging & Code Analysis
Analyze a multi-threaded code snippet exhibiting thread locking issues, identify the deadlock condition, and rewrite the logic to ensure thread-safe execution.
Approach
- Name the four deadlock conditions: mutual exclusion, hold and wait, no preemption and circular wait. Then point to the cycle in the snippet, usually one thread taking A then B while another takes B then A.
- Break the circular wait. Impose a global lock order (by id or address) and acquire locks in that order everywhere, or use std::scoped_lock(a, b) or std::lock, which acquire several mutexes with a deadlock-avoidance algorithm. If the critical section is small, one coarser lock may be simpler.
- Look for the other usual defects: a lock not released on an early return or exception (fix with lock_guard or unique_lock), re-locking a non-recursive mutex in the same thread, calling a callback while holding a lock, and a condition-variable wait without a predicate loop.
- Show how to confirm it: attach gdb, run thread apply all bt, and find the threads blocked in pthread_mutex_lock waiting on each other. ThreadSanitizer also reports lock-order inversions.
- After the fix, re-check every access to the shared data. Removing the deadlock does not remove a data race on a field that is touched outside the lock.
Follow-up
- Write a transfer between two accounts that cannot deadlock, whatever order the callers pass them in.
- What is the difference between deadlock, livelock and starvation? Can try_lock with retry cause livelock?
- Most accesses are reads. How would you restructure the locking?
Given a C program exhibiting infinite execution stalls or Segmentation Faults, identify the memory alignment o
Medium · Debugging & Code Analysis
Given a C program exhibiting infinite execution stalls or Segmentation Faults, identify the memory alignment or uninitialized pointer issue causing the crash.
Approach
- Reproduce it under tooling first. Build with -g -O0 -Wall -Wextra, run it in gdb, and when SIGSEGV arrives use bt, frame N, info locals and print on the suspect pointer. Alternatively set ulimit -c unlimited and run gdb ./prog core.
- Check the usual crash causes: a stack pointer used before it is set, an unchecked NULL from malloc, writes past a buffer (often strlen without the + 1 for the terminator), use after free, writing to a string literal, and unbounded recursion.
- Alignment: casting a char buffer plus an offset to uint32_t * and dereferencing it is undefined. It often works on x86 but can raise SIGBUS on strict-alignment CPUs. The fix is memcpy into an aligned variable.
- For a stall, interrupt it in gdb (Ctrl-C, or gdb -p PID) and read the loop state. Common causes are an unsigned counter tested with i >= 0, = written for ==, a linked list with a cycle, or a loop variable overwritten by a neighbouring buffer overflow.
- Confirm the fix with AddressSanitizer (-fsanitize=address), -fsanitize=undefined (which reports misaligned access), or Valgrind memcheck, which flags reads of uninitialized values.
Follow-up
- The crash goes away when you add a printf. Why?
- How do you find out which line overwrites a variable you can see is corrupted?
- What is the difference between SIGSEGV and SIGBUS?
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
- 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.
- 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.
- 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.
- 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?
Explain why the owner filter ignores the listing index
Medium · composite index · query plans · sort elimination
The only index on resource is (tenant_id, status, updated_at DESC, resource_id DESC). A new endpoint returns one user's resources across all statuses, newest created first: WHERE tenant_id = $1 AND owner_user_id = $2 ORDER BY created_at DESC LIMIT 20. On a tenant with 2M rows it takes 900 ms and EXPLAIN shows a sort above a large scan. Explain precisely why the existing index cannot serve it, give the index that can, and state which of these the new index still will not help: owner_user_id alone across tenants; the same query ordered by updated_at. PostgreSQL 16.
Approach
- Separate the two jobs an index does. For filtering, a composite btree is seekable only on a left prefix, so with no predicate on status the scan can at best range over tenant_id and test owner_user_id per row; PostgreSQL 16 has no btree skip scan to jump the unconstrained column.
- For ordering, the index is sorted by (status, updated_at) within a tenant and not by created_at, so the LIMIT cannot stop early: every matching row is read and then sorted. That is the 'Sort Method: top-N heapsort' line, and it is why the plan reads 2M rows to answer with 20.
- Derive the replacement from the access path — equality, equality, then the ordering column: CREATE INDEX CONCURRENTLY ON resource (tenant_id, owner_user_id, created_at DESC). The scan seeks to the (tenant, owner) range and walks 20 entries in order, so the Sort node disappears along with the row-read.
- Treat INCLUDE (title, status) as conditional, not free. An index-only scan still visits the heap for any row whose page is not marked all-visible, so on a table taking 1.2k writes/second the win depends on autovacuum keeping the visibility map current, and the wider index costs more on every insert.
Follow-up
- 90% of rows are status='active'. Would a partial index WHERE status = 'active' change your answer, and for which of the three queries?
- A dashboard runs this for 40 owners in one page load. What changes about the design?
Implement functions to perform Inorder, Preorder, and Postorder traversals of a binary tree, and explain the a
Medium · Data Structures & Algorithms
Implement functions to perform Inorder, Preorder, and Postorder traversals of a binary tree, and explain the architectural trade-offs between Depth-First Search (DFS) and Breadth-First Search (BFS).
Approach
- Give the recursive forms: preorder visits the node, then left, then right; inorder visits left, node, right; postorder visits left, right, node. Each is O(n) time with O(h) stack, and h can reach n on a skewed tree.
- Write inorder iteratively with an explicit stack: push the whole left chain, pop and visit, then move to the right child. For postorder, either track the last node you visited with one stack, or reverse a node-right-left preorder.
- Mention Morris traversal: it gets O(1) extra space by temporarily threading right pointers to the inorder successor, then restores them, at the cost of extra pointer walks.
- DFS against BFS: DFS needs memory proportional to height and suits path and subtree problems (postorder to free or aggregate a tree, inorder for sorted BST output, preorder to serialize or copy). BFS uses a queue as wide as the widest level, about n/2 at the bottom of a complete tree, and finds minimum depth or the shortest unweighted path first.
- In C, recursion on a deep, degenerate tree can overflow the thread stack. That is the practical reason to know the iterative versions.
Follow-up
- Write postorder iteratively with a single stack.
- Print the tree level by level, one line per level, then in zigzag order.
- Which traversal frees every node safely, and why does preorder free crash?
Explain the step-by-step mechanism of an HTTPS handshake, detailing TLS encryption, certificate verification,
Medium · System Design & Computer Networks
Explain the step-by-step mechanism of an HTTPS handshake, detailing TLS encryption, certificate verification, and session key generation.
Approach
- HTTPS runs over TCP (HTTP/3 uses QUIC instead), so the TCP handshake comes first. In TLS 1.3 the ClientHello carries the random, supported versions and cipher suites, a key_share (an ephemeral ECDHE public key such as X25519), SNI and ALPN.
- The server replies with ServerHello and its own key_share. Both sides compute the ECDHE shared secret and derive handshake keys with HKDF. Everything after that is encrypted: EncryptedExtensions, Certificate, CertificateVerify (a signature over the handshake transcript made with the certificate's private key), and Finished (a MAC over the transcript).
- The client verifies the certificate chain up to a trusted root, the validity dates, the hostname against the SAN, and ideally revocation (for example a stapled OCSP response). It also checks CertificateVerify and Finished, then sends its own Finished. Application traffic keys come from the same key schedule.
- Compare with TLS 1.2: a full handshake takes 2 round trips against 1 in TLS 1.3. The old RSA key exchange encrypted the premaster secret to the server's long-term key and gave no forward secrecy. TLS 1.3 allows only ephemeral (EC)DHE.
- Bulk data uses symmetric AEAD (AES-GCM or ChaCha20-Poly1305). Resumption uses PSK tickets, and 0-RTT early data can be replayed, so it should carry only idempotent requests.
Follow-up
- What exactly stops a man-in-the-middle who can intercept every packet?
- Why does forward secrecy matter if the server's private key leaks a year later?
- What does SNI reveal, and what does Encrypted Client Hello change?
Design a high-throughput, low-latency time-based key-value store capable of handling rapid read and write quer
Medium · System Design & Computer Networks
Design a high-throughput, low-latency time-based key-value store capable of handling rapid read and write queries.
Approach
- Pin the API down: set(key, value, ts), and get(key, t) returns the value with the largest timestamp <= t. Ask whether timestamps arrive in increasing order for each key, and what get should return before the first write.
- Core structure: a hash map from key to an array of (ts, value) pairs. If writes arrive in order, set is an amortized O(1) append and get is a binary search (upper_bound, then step back one) in O(log v). If writes arrive out of order, use a per-key ordered map or skip list, at O(log v) per write.
- Concurrency: shard the key space by hash, with a lock (or a reader-writer lock) per shard. Append-only version arrays let readers run lock-free if a new entry is published only after it is fully written.
- Durability and memory: a write-ahead log plus an in-memory table flushed to sorted files (an LSM layout) favours fast writes. A retention policy (TTL or keep the last N versions) and compaction bound the growth. Chunked arrays avoid latency spikes when a large vector reallocates.
- To scale out, partition keys with consistent hashing and replicate each partition. Then say which reads are allowed to lag the latest write. Edge cases: two writes with the same timestamp (overwrite or reject) and a get earlier than every write.
Follow-up
- Add a range query that returns every version between t1 and t2.
- How do you recover the in-memory state after a crash?
- One key receives most of the writes. What changes?
Walk through the OSI seven-layer model and explain packet encapsulation, routing protocols, and how data moves
Medium · System Design & Computer Networks
Walk through the OSI seven-layer model and explain packet encapsulation, routing protocols, and how data moves across switch fabrics.
Approach
- Name the layers: 1 physical, 2 data link (Ethernet, MAC addresses, VLANs, switches), 3 network (IP, routers), 4 transport (TCP and UDP ports), and 5 to 7 session, presentation and application, which the TCP/IP model folds into one application layer.
- Encapsulation going down the stack: application data becomes a TCP segment (ports, sequence numbers), then an IP packet (source and destination addresses, TTL), then an Ethernet frame (destination and source MAC, EtherType, FCS), then bits on the wire. The receiver strips the headers in reverse order.
- Per hop: a switch learns source MACs and forwards on the destination MAC, flooding unknown unicast. A router removes the L2 header, does a longest-prefix match in its forwarding table, decrements TTL, updates the IPv4 checksum, uses ARP to find the next hop's MAC, and writes a new L2 header.
- Routing protocols fill the routing table, which is then programmed into the forwarding table: OSPF and IS-IS are link-state (each router runs Dijkstra), BGP is path-vector between autonomous systems and is also used in data-centre fabrics, and RIP is distance-vector.
- Two meanings of switch fabric: inside a chassis switch, the ingress line card does the lookup and the packet crosses the fabric to the egress card, with virtual output queues avoiding head-of-line blocking. In a leaf-spine network, ECMP hashes each flow's 5-tuple so a flow's packets stay on one path and arrive in order.
Follow-up
- Trace a ping to a host on the same subnet, then to one on a different subnet. Where does ARP happen in each case?
- What happens when a packet is larger than the MTU of the next link?
- Why does ECMP hash per flow instead of spraying packets across every path?
Design an Object-Oriented software module for a network interface monitor or a distributed device management s
Medium · System Design & Computer Networks
Design an Object-Oriented software module for a network interface monitor or a distributed device management system.
Approach
- Pin the scope: which counters and states to watch (admin and operational state, speed, byte, packet and error counters), how often to sample, and who consumes the results (alerts, a UI, storage).
- Classes: Device owns Interfaces. An Interface holds its state and a CounterSnapshot. A Collector interface (strategy pattern) hides where the samples come from, so a fake collector can drive the tests. A Monitor turns snapshots into rates, a Rule or Policy decides on alerts, and a Subscriber interface (observer pattern) receives link-up and link-down events.
- Rate maths: rate = (c2 - c1) / (t2 - t1). Compute the difference in unsigned arithmetic of the counter's width, so a 32-bit wrap still gives the right answer. Treat a counter that drops after a reset as a new baseline, not a huge negative rate. Debounce link flaps with a hold-down time.
- Concurrency: run a polling thread per device or one event loop, and hand subscribers immutable snapshots so readers never see half-updated counters.
- For the distributed device-management version, a controller stores the desired configuration while agents report the actual state with heartbeats. A reconcile loop applies versioned, idempotent changes, and missed heartbeats mark a device unreachable.
Follow-up
- Add a new interface type without changing Monitor. Which part of the design makes that possible?
- The fleet grows to thousands of devices. Do you push or pull, and where is the bottleneck?
- How do you test the alert rules without real hardware?
Debug a C++ code snippet involving double-free errors, dynamic memory allocation leaks, or improper usage of d
Medium · Debugging & Code Analysis
Debug a C++ code snippet involving double-free errors, dynamic memory allocation leaks, or improper usage of destructor methods.
Approach
- For a double free, check whether a class holds an owning raw pointer but has no user-defined copy constructor and copy assignment. The implicit copy is shallow, so two destructors delete the same memory. The fix is a deep copy, deleted copy operations, or std::unique_ptr (rule of zero).
- Check that allocation and release match: new[] needs delete[], and mixing malloc or free with new or delete is undefined. Also look for a destructor called explicitly on an object that is destroyed again at scope exit.
- Deleting a derived object through a base pointer whose destructor is not virtual is undefined behaviour. In practice the derived destructor is usually skipped, so the derived members leak.
- For leaks, look for paths that skip the delete: early returns, exceptions thrown between new and delete, and maps or containers holding raw pointers that nobody frees. RAII owners close all of these paths.
- Use the tools: AddressSanitizer reports a double free with the allocation and both free stacks, and valgrind --leak-check=full lists definitely-lost blocks with their allocation stack traces.
Follow-up
- Add move semantics to that class. What does the rule of five require, and what state must the moved-from object be left in?
- Two objects hold shared_ptrs to each other and are never freed. Why, and what fixes it?
- Why does setting the pointer to nullptr after delete not prevent every double free?
Demonstrate how to debug a core dump or running process using tools like GDB directly within a terminal enviro
Medium · Debugging & Code Analysis
Demonstrate how to debug a core dump or running process using tools like GDB directly within a terminal environment.
Approach
- Setup: build with -g (variables may show as optimized out at -O2), allow cores with ulimit -c unlimited, check /proc/sys/kernel/core_pattern to see where they go (with systemd-coredump, use coredumpctl gdb), and load the exact binary that crashed with gdb ./prog core.
- Post-mortem commands: bt full, frame N, info args, info locals, print *ptr, p arr[0]@n to print n elements, x/16xb addr for raw bytes, info registers and list.
- For a running process: gdb -p PID (ptrace permission and kernel.yama.ptrace_scope can block it), info threads, thread apply all bt, then detach. gcore writes a core from a live process without killing it.
- Live stepping: break file.c:42, condition N expr, watch var for a hardware watchpoint on corruption, then next, step, finish and continue.
- Pitfalls: a core from a different build gives nonsense frames (match the build-id), and stripped binaries need their separate debug symbols.
Follow-up
- The variable you need shows as <optimized out>. What can you do?
- Find which code corrupts a struct field that is only bad sometimes.
- A service hangs in production. How do you capture its state without restarting it?
Suggested practice checklist
The plan follows the reported rounds: a terminal setup day, then data structures, hand-built structures and bit operations, the C/C++ memory model, debugging, networking and design, and a final mock session with project stories.
Day 1 — Map the loop and set up a terminal workflow
- Write the four reported rounds in order and mark which of the question groups (data structures, low-level C/C++, debugging, design and networking) you are least ready for.
- In a plain terminal, write a small C program with a deliberate off-by-one bug, compile it with gcc -g -Wall -Wextra -fsanitize=address, and reproduce the AddressSanitizer report.
- Open the same binary in GDB and practise break, run, bt, frame, print, x and watch, then write each command on a one-page cheat sheet.
Deliverable: A one-page loop map with the weakest group marked, plus a working compile-and-debug command sheet that you tested on the buggy program.
Day 2 — Strings, trees and the first missing positive
- Write the string compression function in C over a char array, in place, and decide how a run of length one is handled and when the output buffer is large enough.
- Write inorder, preorder and postorder traversals recursively and with an explicit stack, add a queue-based BFS, and note the stack-depth versus queue-width memory trade-off.
- Write the root-to-leaf path-sum check, with an explicit leaf test, and try it on an empty tree and a single node.
- Write the first missing positive solution by swapping each value to its index, and work out the sorted-array version.
Deliverable: Four compiled source files, one per problem group, each with a test main covering empty input and single-element input, and a one-line complexity note per function.
Day 3 — Build structures from scratch and bit operations
- Build an LRU cache in C or C++ from a hash table and a doubly linked list, with get and put in O(1), and decide who frees evicted nodes.
- Write a generic stack with push, pop and peek, deciding how it grows and how it behaves when popped empty.
- Write bit functions: toggle bit k with XOR, test and clear a bit, and count set bits with the clear-lowest-set-bit loop, checking the cases 0 and the all-ones value.
- Write the linked-list removal of every node holding a target value using a Node** parameter, and run it under Valgrind to confirm nothing leaks.
Deliverable: A tested LRU cache, a stack, a bit-operation file and a linked-list removal function, each clean under AddressSanitizer or Valgrind.
Day 4 — The C and C++ memory model
- Print sizeof and offsetof for a struct in two member orders, then add a packed attribute and explain which loads become unaligned and why network header parsing uses memcpy or byte reads.
- Write the same swap by value, by pointer and by reference, then write a dangling pointer case and show the invalid read in AddressSanitizer.
- Compare sizeof for a class with and without a virtual function, and write out the object layout, vptr and vtable for a base and derived class.
- Write a one-page explanation of the stages from source to running process (preprocess, compile, assemble, link, load) against an interpreter executing source or bytecode.
Deliverable: Four short notes, each with a code snippet and its measured output, covering struct layout, references and pointers, vtables, and compiled versus interpreted execution.
Day 5 — Debugging deadlocks, crashes and leaks
- Write a two-thread program that takes two mutexes in opposite order, reproduce the hang, inspect it with thread apply all bt in GDB, and fix it with one global lock order and again with std::scoped_lock.
- Write a C program that crashes from an uninitialized pointer, find the faulting line in GDB, and fix it.
- Write a C++ class that frees its buffer twice and one that leaks in an error path, then find both with AddressSanitizer and Valgrind and repair them with clear ownership or RAII.
- Enable core dumps with ulimit -c unlimited, crash a program, and read the backtrace with gdb ./prog core.
Deliverable: Four broken programs with a written diagnosis for each (hypothesis, evidence, fix) and the tool output that proved the fix.
Day 6 — Networking and design
- Write out the HTTPS handshake in order, covering the TCP connection, hello messages, certificate chain verification, key exchange and session key derivation, and note what TLS 1.3 shortens.
- Design a time-based key-value store: a sorted list of timestamp and value pairs per key, binary search for the latest entry at or before a time, sharded locks, and a statement of what readers see during writes.
- Explain the OSI layers with one frame's encapsulation from application data to Ethernet, and how a switch forwards using its address table while a router uses its routing table.
- Sketch an object-oriented network interface monitor: interface, poller and event classes, how new device types are added, and how failures and thread safety are handled.
Deliverable: Four one-page outlines, each rehearsed aloud once, covering the handshake, the key-value store, the OSI and encapsulation walkthrough, and the monitor class design.
Day 7 — Mock loop and project stories
- In a terminal with no IDE, solve one cache or stack problem and one deadlock-debugging problem back to back, narrating complexity and memory as you go.
- Write a code-level walkthrough of your strongest C or C++ project: what you owned, the hardest constraint, one bug you debugged and what you would change.
Deliverable: A recorded or written mock session with a list of mistakes, plus a project walkthrough and two behavioral stories you can tell without notes.
Project walkthroughs and team alignment
Candidates report that leadership and managerial conversations in this loop centre on your past technical projects, design concepts and how you fit with the team. The role is described as close collaboration with hardware engineers, system test engineers and technical support, so expect to talk about owning a feature from design to release and about working with people outside software. Prepare stories at code level: name the data structure, the bug or the trade-off, not only the outcome.
- Describe the most technically demanding C or C++ project you have owned: the constraint that shaped the design (memory, latency or concurrency), the alternative you rejected and why, and what you would change now.
- Tell me about a bug that appeared only occasionally across threads or processes. How did you reproduce it, which tools did you use, what was the root cause, and what change stopped it coming back?
- Describe a design disagreement with a teammate or a partner team such as hardware or test. State your position as a concrete technical claim, what evidence you brought, and what happened after the decision was made.
- Tell me about a time you worked with people outside your discipline, such as hardware, test or support, to reproduce a customer-facing problem. What did you ask first, and how did you share what you found?
- Describe a code review that changed your approach. What was the feedback, what did you change in the code, and what do you now check before sending a change for review?
Reverse your own decision and price the reversal
Medium · reversibility · measurement · migrations
Describe a technical decision you made and later reversed. Pick one that cost something: a service you split and merged back, a cache you added and removed, an index you created that pushed the planner onto a worse plan, a projection you rebuilt from scratch. State what you believed when you decided, the measurement that changed your mind, how long the wrong version ran in production, and what the reversal cost in migrations, dual writes, and a deprecation window for callers you did not own.
Approach
- State the original rationale without irony, in the version you would still defend given what was known then. If it is not defensible, the story is about carelessness rather than judgement, and a different example serves you better.
- Give the measurement that moved with a before and after: the p99 that did not improve, the cache hit rate that sat at 40%, the plan that flipped to a sequential scan once the table passed a size you can name.
- Cost the reversal in steps, not adjectives: expand-and-contract deploys, the dual-write window, the callers who had to be notified, the rows already written in the wrong shape that had to be backfilled or abandoned.
- Distinguish reversal from rewrite by naming what you kept. Most good reversals preserve the schema or the interface and undo one decision inside it, which is also why they were affordable.
Follow-up
- What in that decision was irreversible, and did you know it was irreversible when you made it?
- How did you tell the people who had already built on top of the original decision?
Argue against a design, lose, and commit anyway
Medium · disagreement · service boundaries · decision records
Describe a design you argued against and lost. State the failure you predicted as a named mechanism, not a feeling about complexity: two services that would need one transaction, a projection with no rebuild path, a write path with no idempotency key. Say what evidence you brought, what the decision maker weighed instead, and what you did after the decision was made: what you instrumented, what you wrote down, and whether the prediction came true. Five minutes.
Approach
- State the prediction in falsifiable form up front: the mechanism, the condition that triggers it, and the observable outcome. A prediction that cannot be checked also cannot be credited to you later.
- Show the evidence you had at the time and label each piece honestly as measured, analogous, or intuition. Keeping the intuition is fine; disguising it as data is the thing that erodes your standing in the next argument.
- Represent the opposing case at full strength, including the constraint you did not control: a fixed date, a team boundary, or the fact that the decision was cheap to reverse and yours was not.
- Make disagree-and-commit concrete. Name the artefact you left behind so the prediction could be settled without you: the alert and its threshold, the counter on the dashboard, the decision note that recorded the trade-off and the condition that would revisit it.
Follow-up
- What threshold on that alert would have proved you right, and did anyone ever look at it?
- If the same proposal arrived tomorrow with the same deadline, would you argue it the same way?
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
- 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.
- 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.
- 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.
- 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?