cswork / libraryOpen practice workspace

Interview guides · ARi

ARi Software Engineer Interview Guide

ARi Software Engineer interview guide: five reported rounds, C/C++ and OOP fundamentals, algorithms, testing and domain questions, plus a 7-day plan.

The 60-second brief

Candidates describe the ARi Software Engineer role as project work where software meets mechanical, automotive and manufacturing systems. The work listed for it includes control systems, automation testing and C++ development for specialised machinery. Engineers design, build and test the software, usually inside a multi-disciplinary team alongside architects, project managers and client stakeholders. The exact assignment changes from project to project, but the day-to-day covers writing code, testing it, taking part in code reviews, documenting the work and, on some projects, helping integrate the software with hardware.

This guide is for candidates interviewing for a Software Engineer position at ARi. It covers the five reported rounds, the fundamentals and algorithm questions candidates report (arrays and linked lists, binary search trees, C macros, OOP and threading, quicksort, subarray sums), the project and testing questions (BDD Cucumber, MIL/SIL/HIL, communication protocols), and the behavioral stories those project discussions call for. Candidates say the content depends on the specific project and client, so use it as a pattern to adapt, not a fixed script. It does not cover pay or a particular client's stack.

Candidates report five rounds over roughly 4-6 weeks: Initial Screening, Technical Assessment, Technical Interviews, Client Round and Manager/HR Discussion. Reports say the content shifts with the project and the client.

Focus areas

  • OOP / OOPS Concepts
  • Data Structures (Arrays, Linked Lists)
  • API Testing

Overview

The requirements candidates describe put C or C++, object-oriented programming and the software development lifecycle at the centre, with Selenium, Cucumber, CAN and automotive or manufacturing knowledge listed as nice-to-haves. That gives you two tracks to prepare. The first is language and computer-science fundamentals you can write and explain without an IDE. The second is a set of project stories about testing, protocols and embedded or industrial systems that you can defend line by line.

The reported questions fall into three groups. The fundamentals group mixes data structures (array versus linked list, binary search tree insertion and deletion) with language topics (macros in C, OOP principles including inheritance and threading) and one web question about static versus dynamic pages. The project group asks about communication protocols or control systems, BDD Cucumber, past project challenges, MIL, SIL and HIL testing, and manufacturing, automotive or embedded design. The algorithm group covers reversing a linked list, the time complexity of quicksort, the largest sum contiguous subarray, finding factors of a quadratic equation, and swapping within arrays and strings. Reported topic frequency points to OOP, arrays and linked lists, API testing, general problem solving and Java.

Several stages are conversations about your own work, including a Client Round that checks fit with a specific project and a final Manager/HR Discussion. Candidates are told to expect their resume to be the starting point for technical deep-dives, and to clarify work location and project expectations early because some roles involve client-site work or travel. In practice that means choosing three or four projects, knowing the architecture and your own contribution to each, and being able to explain them in plain language to someone who is not an engineer.

The ARi interview process

Candidates describe five rounds, from a screening through technical stages and a client conversation to a manager or HR discussion. The wording below follows those reports, and the detail varies by project.

Initial Screening

Candidates describe the opening stage as a screening that checks your technical baseline. Reports do not specify its format, so prepare for a conversation that touches both your resume and short fundamentals questions. Treat it as the point where the match between your background and the role's must-have skills (C or C++, OOP, software development lifecycle) has to be obvious.

What to demonstrate

  • Whether your technical baseline fits the role, as the screening is described.
  • How clearly you summarise your background and which projects you choose to lead with.
  • Basic fluency in the core language on your resume.

How to prepare

  • Write a short spoken summary of your resume that links each major project to C or C++, OOP or testing work.
  • Be ready to answer the simplest fundamentals aloud in a few sentences each: array versus linked list, what a C macro is, the OOP principles.
  • Prepare questions about the project type, work location and any client-site or travel expectations, since candidates are advised to raise these early.

Technical Assessment

This is reported as an early technical evaluation of practical ability. Candidates also say basic data structures and algorithms are common in early technical assessments. The exact format is not reported, so practise both writing working code from scratch and giving short conceptual answers without leaning on an IDE.

What to demonstrate

  • Practical coding ability, shown by code that runs and handles edge cases.
  • Command of basic data structures and algorithm complexity.
  • Core language knowledge in C, C++ or Java.

How to prepare

  • Hand-write the algorithm set candidates report: reverse a linked list, largest sum contiguous subarray, swaps within arrays and strings, and a quadratic-factor routine, each with tests for empty and single-element input.
  • Know quicksort's average case, worst case and what triggers the worst case, and be able to name a pivot strategy that avoids it.
  • Practise binary search tree insertion and deletion, including deleting a node with two children.

Technical Interviews

Candidates describe interviews with technical leads or architects who assess your expertise. Expect the conversation to move between core concepts and the design decisions behind work on your resume. Candidates are advised to explain why they chose an approach, not just what they built, so prepare reasons and rejected alternatives for each technical choice you mention.

What to demonstrate

  • Depth of expertise in the technologies you list.
  • Reasoning behind design choices in past projects.
  • Understanding of object-oriented principles, including how inheritance and threading interact.

How to prepare

  • Prepare a spoken walk-through of OOP covering encapsulation, inheritance, polymorphism and abstraction, then extend it to threading with a race condition and its fix.
  • Review memory management in C or C++: stack versus heap, ownership, leaks and dangling pointers.
  • Draw the architecture of your strongest project from memory and mark the two decisions you would defend and the one you would change.

Client Round

The Client Round is reported as focused on your fit for a specific project's requirements. Because the content depends on the client, the best preparation is to learn what the project involves and select the experience that matches. Candidates are also told that explaining technical ideas to people outside your team matters for client-facing work.

What to demonstrate

  • How well your experience lines up with the specific project's requirements.
  • Ability to explain technical work clearly to a non-peer audience.
  • Readiness to work independently in a client environment.

How to prepare

  • Read the project description closely and list which of your past projects match each requirement, flagging gaps you can address honestly.
  • If the project is automotive or manufacturing, refresh the relevant protocols and testing levels, including CAN and MIL, SIL and HIL.
  • Practise a plain-language explanation of your most relevant project that avoids acronyms until you have defined them.

Manager/HR Discussion

The final stage is reported as discussions with managerial or HR representatives to assess cultural alignment. Candidates describe it as more conversational and project-focused than the technical rounds. Bring structured stories using Situation, Task, Action and Result, and have your practical questions ready about location, travel and project expectations.

What to demonstrate

  • Cultural alignment, as the final discussion is described.
  • How you describe ownership, collaboration and handling change in past projects.
  • Whether the practical terms of the project and role are settled.

How to prepare

  • Rehearse STAR stories for a requirement change mid-project, a hard debugging session and a project challenge you resolved.
  • Prepare an answer on how you work with project managers and client stakeholders, using a real example.
  • Write down questions about work location, client-site work and travel so you leave with them answered.

Where candidates lose points

Listing Selenium, Cucumber, HIL or CAN on your resume with no project behind it

The requirements list these as nice-to-haves and candidates are told every resume line can open a deep-dive. Keep only tools you used, and for each prepare one concrete example: the feature file you wrote and what it checked, the signal you decoded, or the rig a test ran on. For anything you only studied, say so and describe what you learned.

Defining array versus linked list without the trade-offs

Do not stop at 'contiguous versus nodes'. State the costs: O(1) index access versus O(n), insertion at a known node versus shifting elements, cache locality, and the per-node pointer overhead. End with a case where you would choose each.

Leaving gaps in pointer-heavy code: binary search tree deletion and linked-list reversal

For deletion, handle a leaf, a node with one child and a node with two children (replace with the in-order successor), and return the updated root. For reversal, keep previous, current and next pointers, and test an empty list, one node and two nodes before you say you are done.

Saying quicksort is O(n log n) and stopping, or treating a C macro like a function

Give the average case, the O(n²) worst case with its cause (a poor pivot on sorted or equal-heavy input) and the fix. For macros, show the pitfall: SQUARE(a+1) defined as x*x expands wrongly without parentheses and evaluates arguments twice, so compare it with an inline function or const.

Telling project stories as 'we' with no personal decision, or in jargon a client contact cannot follow

For each story, say what you decided and why. Open with a plain-language sentence about what the system does, and define acronyms such as MIL, SIL and HIL before using them. This matters most for the Client Round and the Manager/HR Discussion.

Questions & practice

Explain the difference between an array and a linked list.

Medium · Technical Fundamentals and Programming

Approach

  1. Start with memory layout: an array is one contiguous block, so element i sits at base + i * sizeof(element). A linked list is separate nodes, and each node holds its value plus a next pointer (and a prev pointer if doubly linked).
  2. Compare operations. Indexing is O(1) in an array and O(n) in a list. Inserting or deleting in the middle of an array is O(n) because of shifting. In a list it is O(1) once you hold the right node (the predecessor, for a singly linked list), but finding that node is O(n).
  3. Cover growth. A static array has a fixed size. A dynamic array such as std::vector or ArrayList appends in amortised O(1) by growing geometrically and copying. A list grows one allocation at a time, and each node pays pointer overhead plus allocator overhead.
  4. Explain why arrays usually win in practice: contiguous data uses cache lines and the hardware prefetcher well, while list traversal chases pointers and misses cache. In embedded C, fixed arrays also avoid heap fragmentation.
  5. Say when a list is the right choice: frequent splicing at known positions, iterators that must stay valid (std::list iterators survive inserts and erases of other elements, while vector iterators are invalidated when it reallocates), or an LRU cache built from a hash map plus a doubly linked list.

Follow-up

  • Why is std::vector often faster than std::list even for inserts in the middle?
  • How would you detect a cycle in a linked list, and in what space?
  • How would you implement an LRU cache with O(1) get and put?

How does a binary search tree work? Implement insertion and deletion.

Medium · Technical Fundamentals and Programming

Approach

  1. State the invariant: every key in the left subtree is smaller than the node and every key in the right subtree is larger. Decide up front how duplicates are handled (reject them, or always send them right). An in-order traversal then visits keys in sorted order.
  2. Insertion: walk down from the root, going left or right by comparison, and attach a new node at the first null child. In C, a recursive function that returns the subtree root, or an iterative loop over a Node** link, avoids special-casing the empty tree.
  3. Deletion has three cases. A leaf is unlinked and freed. A node with one child is replaced by that child. A node with two children takes the key of its in-order successor (the minimum of its right subtree), and that successor, which has at most a right child, is then deleted from the right subtree.
  4. Complexity is O(h) for search, insert and delete. That is O(log n) when the tree is balanced, but sorted insertions degrade it to a linked list with h = n. Self-balancing trees (AVL, red-black) keep h at O(log n), and std::map and Java's TreeMap are typically red-black trees.
  5. Test by deleting the root, a node with two children, a leaf and a key that is not present, and by working on an empty tree. After each operation, check that the in-order output is still sorted and that the C version leaks no memory.

Follow-up

  • How would you check that a binary tree is a valid BST? (Pass down min/max bounds, not just a parent comparison.)
  • Find the kth smallest element. What changes if each node stores the size of its subtree?
  • Recursive or iterative: what happens to the stack on a degenerate tree with a million nodes?

Explain the principles of OOPS (Object-Oriented Programming), including inheritance and threading.

Medium · Technical Fundamentals and Programming

Approach

  1. Name the four principles, each with a concrete example. Encapsulation: private state that changes only through methods that keep invariants. Abstraction: interfaces or abstract classes. Inheritance: is-a reuse. Polymorphism: overloading or templates at compile time, and overriding through virtual dispatch at runtime.
  2. Inheritance details by language. In C++, a base class that is deleted through a base pointer needs a virtual destructor (otherwise the behaviour is undefined), and the diamond problem is solved with virtual inheritance. Java allows one superclass and many interfaces, and its non-static, non-final, non-private methods are virtual by default.
  3. Say when not to inherit: use composition for has-a relationships, and respect Liskov substitution (a subclass must work anywhere its base type is expected). Square extending Rectangle is the classic violation.
  4. Say plainly that threading is not an OOP principle, then connect the two. In Java, implement Runnable or submit tasks to an ExecutorService rather than extending Thread, which uses up the single superclass. In C++, std::thread takes any callable, including a member function bound to an object.
  5. Shared mutable object state needs synchronisation. counter++ is a read-modify-write race, so guard it with synchronized or ReentrantLock in Java, or std::mutex with std::lock_guard in C++, or use atomics. Immutable objects are thread-safe by construction, and a consistent lock order prevents deadlock.

Follow-up

  • Overloading or overriding: which is resolved at compile time, and how does a vtable work?
  • Abstract class or interface: when would you choose each?
  • How would you make a singleton thread-safe in Java or in C++11?

Write a function to reverse a linked list.

Medium · Problem Solving and Algorithms

Approach

  1. Use the iterative three-pointer method. Set prev = NULL and curr = head. On each step, save next = curr->next, point curr->next at prev, then move prev = curr and curr = next. When curr is NULL, prev is the new head. This is O(n) time and O(1) extra space.
  2. Make the signature clear. In C, either return the new head or take a Node** and update the caller's head pointer. Simply reassigning a local copy of head does nothing for the caller.
  3. Check the edge cases aloud: an empty list, one node, two nodes. Make sure the old head ends with next = NULL so the list has no cycle.
  4. Offer the recursive version only with its cost. After reversing the rest, set head->next->next = head and head->next = NULL. It uses O(n) stack, which risks overflow on long lists or small embedded stacks.
  5. Test by printing the list before and after, checking that the length is unchanged and that reversing twice gives back the original order.

Follow-up

  • Reverse the list in groups of k nodes.
  • Reverse only the nodes between positions m and n.
  • Check whether a singly linked list is a palindrome in O(1) extra space.

What is the time complexity of quicksort?

Medium · Problem Solving and Algorithms

Approach

  1. Give all three cases. The average and best cases are O(n log n), from the recurrence T(n) = 2T(n/2) + O(n) when partitions are roughly balanced. The worst case is O(n^2), from T(n) = T(n-1) + O(n) when every partition is maximally unbalanced.
  2. Name what triggers the worst case: an already sorted or reverse-sorted input when the first or last element is the pivot, or many equal keys with Lomuto partitioning, which puts every element equal to the pivot on one side.
  3. Give the mitigations: a random or median-of-three pivot, three-way partitioning (Dutch national flag) for duplicates, insertion sort for small subarrays, and introsort, which switches to heapsort past a depth limit. C++ std::sort implementations typically use introsort.
  4. Cover space: quicksort partitions in place, but the recursion uses O(log n) stack on average and O(n) in the worst case. Recursing into the smaller partition and looping over the larger one caps the stack at O(log n).
  5. Compare it with the alternatives. Quicksort is not stable. Merge sort is stable and always O(n log n), but needs O(n) extra space. Heapsort is in place and O(n log n) in the worst case, but uses cache poorly. Java sorts primitives with dual-pivot quicksort and objects with TimSort, which is stable.

Follow-up

  • Write the partition function. How do Lomuto and Hoare partitioning differ?
  • Use the partition step to find the kth smallest element (quickselect). What are its average and worst-case costs?
  • Why is quicksort usually faster than heapsort in practice when both are O(n log n)?

Find the largest sum contiguous subarray.

Medium · Problem Solving and Algorithms

Approach

  1. Start from brute force. Checking every (start, end) pair with a running sum is O(n^2), and it is O(n^3) if each sum is recomputed from scratch.
  2. Use Kadane's algorithm. For each x: cur = max(x, cur + x) and best = max(best, cur). That is O(n) time and O(1) space. Initialise both values with the first element, not 0, so an all-negative array returns its largest element instead of 0.
  3. If the subarray itself is needed, track indices: record a tentative start whenever cur restarts at x, and save start and end whenever best improves.
  4. Handle the edge cases: an empty input (decide whether to return an error or a sentinel), all negatives, a single element, and overflow. Use long long or long when sums can exceed the int range.
  5. Mention the prefix-sum view: the answer is the largest prefix[j] - min(prefix[i]) over i < j. A divide-and-conquer version runs in O(n log n), which shows why the linear scan is preferred.

Follow-up

  • Maximum product subarray: why do you need to track both the running minimum and the running maximum?
  • Circular array: why does max(Kadane, total - minimum subarray) work, and when does it break?
  • Maximum-sum submatrix in a 2D grid: how does Kadane extend to it, and what does it cost?

Write a program for finding factors in a quadratic equation.

Medium · Problem Solving and Algorithms

Approach

  1. Clarify the task first: computing the roots of ax^2 + bx + c (and writing it as a(x - r1)(x - r2)), or factoring a quadratic with integer coefficients into integer linear factors. Then state the input types.
  2. For roots, compute the discriminant d = bb - 4a*c. If d > 0 there are two real roots, if d == 0 one repeated root -b/(2a), and if d < 0 two complex roots with real part -b/(2a) and imaginary part ±sqrt(-d)/(2a).
  3. Handle degenerate input. When a == 0 the equation is linear (x = -c/b). When a and b are both 0 there is no solution if c != 0, and every x is a solution if c == 0. Compute bb - 4a*c in a wider type (long long or double) so integer coefficients do not overflow.
  4. For numerical stability, avoid subtracting nearly equal numbers when b^2 is much larger than 4ac. Compute q = -0.5 * (b + sign(b) * sqrt(d)), then x1 = q / a and x2 = c / q, with a guard for q == 0. Compare floating-point values with a tolerance, not ==.
  5. For integer factoring (the ac method), look for integers p and q with p * q = a * c and p + q = b by testing divisors of ac up to sqrt(|ac|). If none exist, the polynomial does not factor over the integers. Write one test per case: two roots, a repeated root, complex roots, a = 0.

Follow-up

  • Why can the textbook formula lose precision, and how does the alternative form fix it?
  • How would you print rational roots in lowest terms instead of as decimals?
  • Which unit tests would you write to cover every branch?

Solve coding problems related to swapping arrays and strings.

Medium · Problem Solving and Algorithms

Approach

  1. Write a correct swap first. In C, pass pointers: void swap(int *a, int *b) with a temporary variable. Know why the tricks are risky: an XOR swap zeroes the value when both pointers refer to the same location, and an add/subtract swap can overflow.
  2. Reverse an array or string in place with two pointers, swapping a[i] and a[j] while i < j. That is O(n) time and O(1) space. In C, operate on a char array, never on a string literal (modifying a literal is undefined behaviour), and leave the '\0' terminator where it is.
  3. Know the language differences. Java Strings are immutable, so convert to char[] or use StringBuilder. In C++, std::swap on two std::vector objects is O(1) because it swaps internal pointers, while swapping C arrays or std::array objects is O(n) element by element.
  4. Cover the common variants: swapping adjacent pairs; rotating an array by k with three reversals (the whole array, then the first k, then the rest) after setting k %= n; and reversing the words of a sentence by reversing the whole string and then each word.
  5. Test the edge cases: empty input, length 1, odd and even lengths, k = 0 or k >= n for rotation, and repeated characters.

Follow-up

  • What is the minimum number of swaps needed to sort an array of distinct values? (Think in permutation cycles.)
  • Can two strings be made equal with exactly one swap? Handle the case where they are already equal.
  • Rotate an array by k in place without using an extra array.

Hold a per-tenant active cap against concurrent creates

Hard · write skew · isolation levels · constraints

A tenant on the standard plan may hold at most 50 resources with status='active'. The create handler runs SELECT count(*) FROM resource WHERE tenant_id = $1 AND status = 'active', compares to 50, then inserts. Two creates arrive 3 ms apart on different instances and the tenant lands at 51. Name the anomaly, say whether PostgreSQL 16 READ COMMITTED or REPEATABLE READ prevents it and why, then give an implementation that holds the cap at READ COMMITTED with the exact statements. Finally, say what changes when the cap is 'at most one running export per tenant' on job_run.

Approach

  1. Name it: write skew. The two transactions read an overlapping set and write disjoint rows, so there is no row-level conflict for the engine to detect and each commit is individually legal.
  2. Rule out the levels precisely. READ COMMITTED takes a fresh snapshot per statement and takes no lock on the counted rows, so both see 49. PostgreSQL's REPEATABLE READ is snapshot isolation: it removes non-repeatable reads and phantoms within the snapshot but still admits write skew, because the anomaly is not a re-read of a changed row, it is a read of a set that a concurrent transaction invalidates. Only SERIALIZABLE closes it, by tracking the read dependency and aborting one transaction with SQLSTATE 40001 — a guarantee that exists only if the application re-runs the whole transaction from the read.
  3. Convert the set predicate into a single-row conflict: keep tenant.active_resource_count and run UPDATE tenant SET active_resource_count = active_resource_count + 1 WHERE tenant_id = $1 AND active_resource_count < 50 in the same transaction as the INSERT. Zero affected rows is the cap, returned as 409. The row lock serialises the decision at any isolation level, and contention is bounded to one tenant's row — which is also the fair-scheduling unit, unlike a global counter that would convoy every tenant behind one row.
  4. State the cost you just took on: a counter is a second source of truth that can drift, so every path that changes status must adjust it inside the same transaction, and a periodic reconciliation has to exist, with resource_revision as the authority for what the count should have been.

Follow-up

  • A resource moves from archived back to active. Which statements change, and what breaks if the counter update and the status change land in different transactions?
  • The cap becomes plan-dependent and a plan can change mid-month. Where does the number 50 live, and who reads it?

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 is the difference between static and dynamic web pages?

Medium · Technical Fundamentals and Programming

Approach

  1. Static page: a file (HTML, CSS, JS, images) that the web server or a CDN returns byte for byte. Every visitor gets the same response, and changing the content means changing and redeploying the file.
  2. Dynamic page: server-side code (for example a Java servlet, PHP, Node or Python) builds the response for each request, often from a database, so the output depends on the user, the session, cookies or query parameters.
  3. Separate client-side dynamism from server-side dynamism: a static HTML file can still be interactive if JavaScript fetches data from an API after it loads. The distinction is where the HTML is produced and when.
  4. Trade-offs: static pages are cheap, very cacheable, fast and expose little attack surface. Dynamic pages allow personalisation and live data, but they need application servers and a cache strategy, and they open risks such as SQL injection and XSS that need input validation and output encoding.
  5. Mention the middle ground: static site generators build pages at deploy time, and dynamic responses can be cached at the server or CDN with a TTL or Cache-Control headers so that most requests never reach application code.

Follow-up

  • Walk through what happens between typing a URL and the page rendering (DNS, TCP, TLS, HTTP request, rendering).
  • How would you cache a page that is mostly the same for everyone but shows the user's name?
  • How do sessions and cookies keep state across HTTP requests, which are stateless?

Can you define macros in C?

Medium · Technical Fundamentals and Programming

Approach

  1. Define it: a macro is a #define that the C preprocessor expands by text substitution before the compiler runs. An object-like macro: #define BUF_SIZE 256. A function-like macro: #define SQUARE(x) ((x) * (x)). Show the expansion with gcc -E.
  2. Name the pitfalls. Parenthesise every parameter and the whole body, or SQUARE(a + 1) expands wrong. Arguments can be evaluated twice: SQUARE(i++) modifies i twice without sequencing, which is undefined behaviour. Macros have no type checking and no scope.
  3. Wrap multi-statement macros in do { ... } while (0) so they act as one statement, including after an if with no braces and before an else.
  4. Cover the other preprocessor tools: # turns an argument into a string, ## pastes tokens, VA_ARGS (C99) takes variadic arguments, #ifdef/#ifndef handle conditional compilation and include guards, and FILE and LINE are useful in logging macros.
  5. Give the trade-off: static inline functions and const or enum constants give type checking and are visible in a debugger, so prefer them for logic. Macros remain the normal tool for include guards, platform switches and embedded register access such as #define REG (*(volatile uint32_t *)0x40021000).

Follow-up

  • Write a MAX macro. What goes wrong with MAX(i++, j)?
  • What is the difference between a #define constant and a const variable?
  • Why does a memory-mapped register need volatile?

Explain the BDD Cucumber framework and how you have applied it in your projects.

Medium · Domain-Specific and Project Experience

Approach

  1. Explain BDD: behaviour is written as concrete examples in Gherkin inside .feature files. The keywords are Feature, Background, Scenario, Given/When/Then/And, and Scenario Outline with an Examples table. Non-developers can read the files, so they double as acceptance criteria.
  2. Explain the mechanics. Step definitions are methods annotated @Given, @When or @Then whose Cucumber expressions or regexes bind parameters ({int}, {string}). The glue path tells the runner where they live, a runner such as a JUnit 4 class with @CucumberOptions sets features, glue and tags, and @Before/@After hooks handle setup, teardown and screenshots on failure.
  3. Describe your own layering, and only what you built. Keep steps thin and delegate to page objects (Selenium) or an API client. Share scenario state through dependency injection (for example PicoContainer) rather than static fields. Use tags such as @smoke and @regression to pick suites in CI.
  4. Name the pitfalls you handled: scenarios written as UI click scripts instead of behaviour, duplicate or ambiguous step definitions, scenarios that depend on each other, and flaky waits. Fix waits with explicit conditions instead of Thread.sleep.
  5. Close with numbers from your project: how many scenarios, what they covered (UI, API), how they ran in the pipeline, and one defect they caught or one maintenance cost they cut.

Follow-up

  • When would you use a Scenario Outline instead of separate scenarios?
  • How do you share state between two step definition classes in one scenario?
  • A scenario passes locally but fails one CI run in ten. How do you find out why?

What challenges have you faced in previous projects and how did you resolve them?

Medium · Domain-Specific and Project Experience

Approach

  1. Pick one technical problem you personally drove, such as an intermittent failure, a performance regression, a hardware or interface integration issue, or a requirement that changed late. Set the context in two sentences.
  2. Walk through the diagnosis in order: how you reproduced it, how you narrowed it down (logs, a debugger, bisecting changes, isolating a module or a bus message), the hypotheses you ruled out, and the evidence that confirmed the cause.
  3. State the fix and the trade-off you accepted, for example a lock that cost some throughput, a schedule slip agreed with the project manager, or a workaround now with a proper fix later. Say who else you had to align with.
  4. Give the result with something measurable (failure rate, latency, defects found, delivery date met) and the lasting change: a regression test, a monitoring check or a review step that stops the problem returning.
  5. Speak as "I" for your own actions. A vague team story, or one where the challenge was simply someone else's mistake, leaves nothing to discuss.

Follow-up

  • How did you confirm the fix worked and was not just hiding the symptom?
  • What did you do when the requirements changed partway through that project?
  • Looking back, what would you do differently from the start?

How do you approach MIL, SIL, or HIL testing?

Medium · Domain-Specific and Project Experience

Approach

  1. Define each level. MIL runs the controller model (for example in MATLAB/Simulink) in a closed loop with a plant model, all in simulation. SIL compiles the production code (generated or handwritten) on the host and runs it against the same plant model. PIL runs it on the target processor or an emulator. HIL runs the real ECU against a real-time simulator that emulates sensors, actuators and buses such as CAN.
  2. Explain why the sequence exists. MIL catches logic and requirement errors at the lowest cost. SIL catches implementation and code-generation differences. HIL catches timing, I/O, scheduling and integration faults that only appear on real hardware.
  3. Describe the method: derive test cases from requirements and keep traceability, reuse the same test vectors at every level, and run back-to-back comparisons (MIL against SIL) with tolerances that account for fixed-point or floating-point differences.
  4. Cover depth on HIL: fault injection (open or shorted sensor lines, out-of-range signals, message timeouts, bus-off), boundary and timing tests, and automated regression runs with reports. Mention structural coverage (statement, decision, MC/DC) where the safety standard for the project requires it.
  5. Anchor it in your own work: the level you tested at, the tools and bench, how many cases, and one defect found at SIL or HIL that MIL could not have revealed.

Follow-up

  • In back-to-back testing, what exactly is compared and how do you choose the tolerances?
  • How would you inject a sensor fault or a CAN message timeout on a HIL bench?
  • What is MC/DC coverage, and how does it differ from decision coverage?

p99 jumped on one listing filter while p50 stayed flat

Medium · query planning · composite index · percentiles

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

Approach

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

Follow-up

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

Suggested practice checklist

The plan follows the five reported rounds: day 1 maps your resume to the role, days 2 to 5 cover data structures, algorithms, language fundamentals and testing, day 6 builds your stories, and day 7 is a full run-through.

Day 1 — Map the loop and your resume to the role

  • Write the five reported rounds in order, and list the requirements candidates describe: C or C++, OOP and the development lifecycle, then Selenium, Cucumber, CAN and automotive or manufacturing knowledge.
  • For each resume project, mark which requirements it supports and where you have a gap.
  • Pick the three projects you can defend in architectural detail.

Deliverable: A one-page matrix of your projects against the role's requirements, with gaps marked and three projects chosen.

Day 2 — Array vs linked list, and the binary search tree

  • Write a table comparing array and linked list on indexing, insertion at head, insertion at a known node, search, memory overhead and cache behaviour.
  • Implement binary search tree insertion and deletion in C++ or Java, covering leaf, one child and two children, and test deleting the root.
  • State the cost in terms of tree height, and explain what happens with already-sorted input.

Deliverable: The comparison table and a working tree with tests, including the degenerate case.

Day 3 — Reported algorithm questions

  • Reverse a linked list iteratively and recursively, and test with zero, one and two nodes.
  • Solve the largest sum contiguous subarray with Kadane's algorithm, including an all-negative array.
  • Write quicksort, state average and worst-case complexity and name a pivot strategy that avoids the worst case.
  • Write a quadratic-factor routine (discriminant, a equal to zero, negative discriminant) and an in-place array and string swap using two indices.

Deliverable: Five solutions with tests and a one-line complexity statement for each.

Day 4 — C macros, OOP and threading, and the web question

  • Write function-like macros, demonstrate the missing-parentheses and double-evaluation pitfalls, and compare each with an inline function.
  • Demonstrate encapsulation, inheritance, polymorphism and abstraction with one small class hierarchy in your language, using a virtual function or interface.
  • Write a shared counter updated by two threads, show the race, then fix it with a mutex or atomic.
  • Write a short answer on static versus dynamic web pages: how each is served and when you would pick each.

Deliverable: One cheat sheet and two runnable snippets (the macro pitfalls and the thread race with its fix).

Day 5 — Testing and domain: Cucumber, MIL/SIL/HIL, API tests

  • Write a Cucumber feature file with Given, When and Then steps for behaviour from your own project, and sketch the step definitions.
  • Define MIL, SIL and HIL in one sentence each, then state what a controller or component from your project would verify at each level.
  • Write test cases for a REST endpoint: status codes, response schema, authentication, and negative inputs.

Deliverable: A feature file with step sketches, a three-level test outline and a list of API test cases.

Day 6 — Stories for the project and manager rounds

  • Write STAR outlines for communication protocols or control systems, and for manufacturing, automotive or embedded design.
  • Write STAR outlines for a project challenge you resolved, a requirement change mid-project and a complex system issue you debugged.

Deliverable: Five written STAR outlines and one spoken run-through of each.

Day 7 — Full run-through by round

  • Give your resume walk-through aloud as you would in the screening.
  • Solve two algorithm problems in a plain editor without autocomplete and explain complexity afterwards.
  • Explain your most relevant project to a non-engineer, then write the questions you will ask about location, travel and project expectations.

Deliverable: A spoken resume walk-through, two timed solutions and a written list of questions for the client and manager.

Project stories for the client and manager rounds

Reports describe the later rounds as built around your past work: the Client Round checks fit with a project's requirements and the Manager/HR Discussion looks at cultural alignment. Candidates also report scenario prompts about a mid-project requirement change and debugging a complex system issue. Prepare stories in which you made the technical decision and can state the result.

  1. What challenges have you faced in previous projects and how did you resolve them?
  2. How did you handle a requirement change mid-project?
  3. Describe a time you had to debug a complex system issue.
  4. Walk through the architecture of your most relevant project, your own contribution, and one technical decision you would make differently.

Describe your experience with communication protocols or control systems.

Medium · Domain-Specific and Project Experience

Approach

  1. Pick one system you actually worked on and name its layers in order: the physical link (for example a CAN bus, a serial line or Ethernet), the message format (identifiers, signal layout, units), and the software on each end. If your experience is a higher-level protocol such as HTTP or MQTT, say so and use it, because the structure of the story is the same.
  2. Spend most of the answer on a decision you made: message rate or bit rate, framing, timeouts and retries, and how you handled a corrupted, lost or late message. On CAN, for instance, you can explain that the lowest identifier wins arbitration and what that meant for how you assigned priorities.
  3. For a control system, describe the loop explicitly: sensor input, controller (a PID loop or a state machine), actuator output and the sample time. Then name one problem you handled, such as noise on the sensor, actuator saturation or integrator windup, and what you changed.
  4. Say how you tested it without the full rig: a simulated node, a replayed log, a bus analyser or a unit-test harness around the parser. Finish with a result you can state, such as messages dropped before and after, latency, or defects found, and one thing you would change.
  5. If your experience is limited, describe a smaller project or coursework honestly and explain how you would extend it. Claiming production experience you cannot detail is the mistake that ends this answer.

Follow-up

  • What happened when a message was lost or arrived late, and how did your software detect it?
  • How did you test it without the real hardware attached?
  • Why did you choose that protocol or control approach over the alternatives?

Discuss your experience with manufacturing, automotive, or embedded design concepts.

Medium · Domain-Specific and Project Experience

Approach

  1. Choose the domain closest to the project you are interviewing for, and open with one sentence naming the system and your module, such as firmware for a controller, test automation for a vehicle component or software on a production line. Then say what you personally owned.
  2. Show you understand embedded constraints with one concrete trade-off you made: static buffers versus heap allocation, polling versus an interrupt, how a watchdog or timing deadline shaped your design. Give the number you measured, such as memory footprint, loop time or worst-case latency.
  3. Connect to how automotive and manufacturing software is verified: requirements traced to tests, then checks at several levels from model and software-only runs up to hardware-in-the-loop, with documentation of results. Mention standards or tools such as MATLAB only if you used them.
  4. Structure it as situation, decision, result. Name the options you considered, why you picked one, and how you confirmed it worked. If a requirement changed late, say how you absorbed it.
  5. Avoid listing terms (RTOS, CAN, MATLAB) you cannot go one level deeper on. For every term on your resume, have a specific example ready.

Follow-up

  • What was the tightest resource or timing constraint, and how did you find out you were close to it?
  • How did you verify the behaviour on real hardware versus in simulation?
  • What would you have done differently if the requirement had changed late in the project?

Unblock an engineer without taking the keyboard

Easy · mentoring · leases · at-least-once

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

Approach

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

Follow-up

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