Interview guides · ACG Consulting Group
ACG Consulting Group Software Engineer Interview Guide
ACG Consulting Group Software Engineer interview guide: four reported rounds, C++ memory and concurrency, industrial topics and a 7-day prep plan.
The 60-second brief
Candidates describe the Software Engineer role as software for industrial applications, in settings such as manufacturing, automation and specialised consulting projects, where the code serves client operations. The work is reported to range from embedded systems and hardware-software integration to enterprise application development. Candidate reports name no specific products or customers, so the only machinery you can prepare for is what the reported questions mention: a tabletting machine, bearings, welding and real-time inter-process communication. In practice the role means writing and maintaining systems-level code, integrating it with hardware teams' equipment, testing it and documenting it.
This guide is for candidates interviewing for the Software Engineer position at ACG Consulting Group. It covers the four reported stages, the C++ and operating-system fundamentals that candidates report (memory allocation, processes and threads, mutexes and semaphores, private constructors, static members), the industrial-equipment questions, the testing-experience question and the behavioral prompts. The role is described as working alongside project managers and application engineers, with duties in development and maintenance, hardware integration, testing and documentation, so the plan prepares you for both technical and cross-discipline conversations.
Candidates report four rounds over roughly 3-5 weeks: Initial Screening, Technical Rounds, Interactions with Leads and Deep-Dive Discussions. Some report the process compressing into a single day of back-to-back interviews.
Focus areas
- Programming in C++
- Memory Management (malloc/free/new/delete)
- Synchronization Primitives (Mutex vs Semaphore)
Overview
The reported questions lean toward systems-level C++ rather than application frameworks. Candidates name C++ as the language that comes up most, with memory management, multithreading and low-level system calls as the advanced areas, and the requirements they describe list data structures, algorithms and memory management as must-haves. Prepare to explain concepts from first principles and to write short C++ by hand: allocate and free an object correctly, protect shared state with a lock, and implement a basic container.
The fundamentals fall into three clusters that you can rehearse as a connected story. Memory: how malloc, calloc and the new operator differ (constructor calls, zero-fill, failure signalling, matching deallocation). Concurrency: process versus thread, mutex versus semaphore, and inter-process communication under real-time constraints. Class design: why a constructor would be private and why a member function would be static. If you can tie each to a component you built, such as a driver, a queue between two tasks or a factory for device objects, the answers stop sounding like recited definitions.
The domain questions are the unusual part of this loop. Candidates report being asked about a tabletting machine, bearing types for given loads and the categories of welding, and the role lists LabVIEW, CNC programming and industrial automation tools as nice-to-haves. You are interviewing for a software job, so the goal is not mechanical-engineering depth. Learn the basic vocabulary, say plainly what you have and have not worked with, and show how you would find out the rest: which sensors, interfaces and constraints the software would touch.
The later rounds are reported to involve technical leads, project managers, HR and then leadership returning to topics from the earlier technical screens. Expect to explain technical issues to non-engineers, describe how you handle disagreement and feedback, say what you want from the next two years, and discuss your testing experience. Choose two or three projects you can describe from requirement to deployment, because those stories will be reused across the middle and final rounds.
The ACG Consulting Group interview process
Candidates describe four stages, from an initial screening to deep-dive discussions with leadership. Treat the order as reported rather than guaranteed, since some report the stages being packed into one day.
Initial Screening
Candidates report the first contact as a phone or video call used to judge fit for the role. Some also mention aptitude and verbal ability tests as an early hurdle. Treat it as a concise pitch: who you are as an engineer, which C++ and hardware-adjacent work you have done, and why this company and this kind of industrial software work.
What to demonstrate
- Overall fit for the Software Engineer role, which is the stated purpose of this stage
- How clearly you summarise your background and C++ experience by phone or video
How to prepare
- Write a short spoken summary of your career that names your C++ projects, any embedded or hardware-facing work, and one outcome you owned end to end.
- Prepare an honest answer to why this company and this type of work, built from what the role description says (industrial applications, hardware integration, testing, documentation) rather than from guesses about the employer.
- If an aptitude or verbal test is part of your process, practise logical reasoning, basic mechanics and reading-comprehension questions beforehand.
Technical Rounds
Candidates describe a series of interviews covering coding basics and specialised domain knowledge, with C++ the language cited most often. This is where the C++ and operating-system fundamentals questions and the industrial-equipment questions are practised. Answer fundamentals with a definition, a concrete example and the failure mode, and say out loud what you assume when a question is open ended.
What to demonstrate
- Working knowledge of C++ memory management and multithreading
- Command of data structures and algorithms through short coding tasks
- Familiarity with the industrial equipment and real-time communication topics that candidates report
How to prepare
- Write a small C++ program that allocates an object with malloc, calloc and new, and note which calls run the constructor, which zero the memory and how each reports failure.
- Implement a bounded producer-consumer queue twice, once with a mutex and condition variable and once with a counting semaphore, and explain ownership in each.
- Make a one-page table of IPC options (pipes, message queues, shared memory with semaphores, sockets) with copy cost, blocking behavior and suitability for real-time work.
- Write half a page each on how a tabletting machine, bearings and welding categories work, marking what you know firsthand and what you only know from reading.
Interactions with Leads
Candidates report conversations with technical leads, project managers and HR representatives, sometimes within a single long day. The audience mixes engineers and non-engineers, so the same project has to be told at two levels: the technical detail for a lead and the outcome and risk for a project manager.
What to demonstrate
- Communication across disciplines, including with project managers
- How you take feedback and handle disagreement about technical direction
- Whether your stated career goals and interest in this team are specific
How to prepare
- Build a testing story naming the frameworks you used, the layers they covered, what you stubbed when hardware was unavailable and a defect the tests caught.
- Rehearse explaining a technical issue, such as a race condition or a memory leak, to a project manager in terms of schedule, risk and fix.
- Write your two-year goal in two sentences and connect it to work on industrial or hardware-integrated software.
- Prepare an example of documentation you wrote, such as a specification or handover note, since the role lists documentation among its duties.
Deep-Dive Discussions
Candidates describe in-depth conversations with leadership built on what came out of the earlier technical screens. Expect to be taken deeper on answers you already gave, so keep notes after each earlier round on what you said and where you hedged. Be ready to defend design choices, not only name them.
What to demonstrate
- Depth behind the technical answers given in earlier screens
- Ownership of a feature or project from conception to deployment
- Clarity about why you want this team and role
How to prepare
- After each earlier round, list every claim you made and rehearse a two-level follow-up for each: why it works, and what happens when it fails.
- Prepare one project end to end: requirement, design choice, trade-off you rejected, test approach, deployment and what you would change.
- Prepare the code-optimisation story in measurable terms: what was slow, how you measured it, what you changed and what the measurement showed afterward.
- Write two questions for leadership about how software and hardware teams share responsibility for integration and testing.
Where candidates lose points
Describing a mutex and a semaphore as the same thing, or a binary semaphore as simply a mutex
State the difference in ownership and purpose: a mutex is locked and unlocked by the same thread to guard a critical section and can support priority inheritance, while a semaphore is a counter that any thread can signal, used to count resources or to signal events. Back it up with a producer-consumer example.
Saying malloc and new both just allocate memory
Name the real differences: new calls the constructor and returns a typed pointer, malloc returns void* with uninitialised bytes, calloc zeroes them, new throws std::bad_alloc where malloc returns NULL, and each allocation needs its matching free, delete or delete[]. Mixing the pairs is undefined behavior.
Bluffing on the tabletting machine, bearing or welding questions
Separate what you know from what you have read, give the general principle you do know (for example axial versus radial load for bearings), and describe how you would learn the rest from the hardware team. Then pivot to the software side: sensors, interlocks, timing.
Answering the testing-frameworks question with a list of tool names
Tell one project story: the framework, the layers, what you stubbed, one defect caught and what you changed afterward. The role lists testing and quality assurance among its duties, so the story needs a decision and a result.
Giving a generic 'why this company' and two-year answer in the lead and leadership conversations
Tie your goal to the work the role describes: industrial applications, hardware integration, ownership from conception to deployment. Do not assert facts about the employer that you have not verified.
Questions & practice
Explain the memory management differences between malloc, calloc, and the new operator in C++.
Medium · Technical Foundations
Approach
- malloc(n) returns a void* to n bytes of uninitialised memory, or NULL on failure. calloc(count, size) returns zero-filled memory for count*size bytes, and mainstream C libraries (glibc, musl) return NULL if that multiplication overflows. Neither one knows about types or runs constructors.
- new is type-aware. It computes the size, calls operator new to get the memory, then runs the constructor, and it throws std::bad_alloc on failure unless you call new(std::nothrow), which returns nullptr. delete runs the destructor and then frees the memory, and new[] must be paired with delete[] so that every element's destructor runs.
- Mixing the families is undefined behaviour: free() on memory from new, delete on memory from malloc, or delete on memory from new[]. realloc only works on malloc-family memory and is unsafe for objects that are not trivially copyable, because it may move bytes without calling copy or move constructors.
- Separate the new expression from operator new: operator new only allocates raw memory and can be overloaded per class (for example to draw from a fixed-size pool), and placement new constructs an object in memory you already own, which you then destroy by calling the destructor explicitly.
- In modern C++ you rarely write new or delete directly. Use std::vector, std::make_unique and RAII. In long-running machine-control code, a common rule is to allocate at startup and keep the heap out of the time-critical path, to avoid fragmentation and unbounded allocation latency.
Follow-up
- What exactly goes wrong if you release a new[] array with delete instead of delete[]?
- How would you overload operator new and operator delete for one class to use a fixed-size memory pool?
- When does calloc's zeroing cost nothing, for example with fresh pages from the operating system?
Can you describe the fundamental differences between a Process and a Thread?
Medium · Technical Foundations
Approach
- A process has its own virtual address space, file descriptor table and other OS resources. Threads inside one process share the address space, heap, globals and open files, and each thread has its own stack, registers, program counter and thread-local storage.
- Trade-offs: a thread is cheaper to create, and switching between threads of the same process skips the address-space switch. Threads can communicate through shared memory directly but need mutexes or atomics to do it safely. Processes need IPC (pipes, sockets, shared memory, message queues) and in return get isolation: a segfault in one thread kills the whole process, while a crashed child process leaves its parent running.
- Show what happens on Linux: threads and processes are both tasks created by clone() with different sharing flags, and fork() copies the parent with copy-on-write pages, so memory is duplicated only when one side writes to it.
- Cover the process life cycle in one line: new, ready, running, blocked/waiting, terminated. A terminated child stays a zombie until its parent reaps it with wait() or waitpid().
- Close with a design choice: put an unreliable vendor driver or a crash-prone parser in its own process so a fault cannot take down the control logic, and use threads for parallel work on shared data where you can reason about the locking.
Follow-up
- What happens if a multithreaded process calls fork(), and why is only async-signal-safe code allowed in the child before exec?
- What is a zombie process, and what is an orphan process?
- When would you choose multiple processes over multiple threads for a machine-control application?
What is the difference between a Mutex and a Semaphore in the context of concurrency?
Medium · Technical Foundations
Approach
- A mutex has ownership. The thread that locks it must be the one that unlocks it, and it protects a critical section. Unlocking std::mutex from another thread is undefined behaviour, and a POSIX error-checking mutex returns EPERM. A semaphore is a counter with wait (P: decrement, block at zero) and post (V: increment), has no owner, and any thread can post it.
- Use each for its job: a mutex for mutual exclusion over shared state, and a counting semaphore for a pool of N identical resources or for signalling between threads. Because a semaphore can be posted by a different thread, it suits ISR-to-task notification in an RTOS, where an interrupt handler cannot own a lock.
- A binary semaphore is not a drop-in mutex. Without ownership, the system cannot apply priority inheritance or detect recursive or foreign unlocks. Explain priority inversion, where a low-priority holder is preempted by a medium-priority task while a high-priority task waits, and how a priority-inheritance mutex (PTHREAD_PRIO_INHERIT) fixes it. The Mars Pathfinder resets are the well-known example.
- Name the APIs: std::mutex with std::lock_guard or std::scoped_lock, C++20 std::counting_semaphore and std::binary_semaphore, POSIX pthread_mutex_* and sem_wait/sem_post.
- Have a worked example ready, such as a bounded producer-consumer buffer: an 'empty slots' semaphore starting at N, a 'full slots' semaphore starting at 0, and a mutex around the buffer indices. Explain why taking the mutex before waiting on a semaphore can deadlock.
Follow-up
- How do you avoid deadlock when a function needs two mutexes at once?
- When would you use a condition variable instead of a semaphore, and why must its wait sit in a loop?
- What is a spinlock, and when is it a better choice than a blocking mutex?
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?
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
- 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.
- 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.
- 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.
- 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?
What are the primary differences between Private Constructors and public ones, and in what scenarios are they
Medium · Technical Foundations
What are the primary differences between Private Constructors and public ones, and in what scenarios are they essential?
Approach
- State the access rule first: a public constructor can be called by any code, while a private one can be called only by the class's own members and its friends in C++ (in Java, only from inside the class and its nested classes). Making it private takes object creation away from outside callers and puts it behind code the class controls.
- Name the scenarios where it is essential: a singleton (C++: a static instance() returning a function-local static, which C++11 initialises thread-safely), named constructors or static factories that validate input and return std::optional or std::unique_ptr instead of half-built objects, and Java utility classes that should never be instantiated (in C++ a namespace is usually the better fit).
- Contrast with deleted functions: before C++11, a copy constructor was declared private and left undefined to block copying. Today '= delete' does the same job, gives a clear compile-time error and also covers friends and members.
- Cover inheritance: a class whose constructors are all private cannot be used as a base, because a derived constructor has to call one of them (unless the derived class is a friend). In Java the same rule stops subclassing outside the class. If the goal is only to block inheritance, C++11 'final' says it directly.
- Edge case: std::make_shared cannot call a private constructor, even from inside a static member, because the library performs the construction. Use the passkey idiom, or shared_ptr<T>(new T) inside the factory, which gives up the single allocation.
Follow-up
- How would you make a C++ singleton thread-safe, and what does a function-local static guarantee since C++11?
- Why do singletons make unit testing hard, and what would you use instead in a hardware-facing codebase?
- How would you let std::make_shared or std::make_unique build an object whose constructor is private?
How do Static member functions differ from standard member functions, and why would you implement them?
Medium · Technical Foundations
Approach
- A static member function has no 'this' pointer. It is called as ClassName::f() without an instance, can directly reach only static members (or members of an object passed to it), and cannot be virtual, const or volatile, because those qualifiers all depend on an object.
- Its address is a plain function pointer, not a pointer-to-member. That lets it act as a callback for C APIs such as pthread_create or a driver's interrupt-registration call, with the object passed through the void* user-data argument and cast back inside the function.
- Typical uses are factory functions that pair with a private constructor, a singleton accessor, helpers that belong with the class but need no instance, and code that manages static data members such as an instance counter.
- Static data members are one copy shared by all instances, so concurrent updates need an atomic or a lock. Since C++17 an inline static member can be defined in the header. Mention the static initialisation order problem across translation units and the function-local static that avoids it.
- Keep the other meanings of 'static' separate: at namespace scope it gives internal linkage, and on a local variable it keeps the value across calls (initialised once, thread-safely since C++11). Neither of those is a static member.
Follow-up
- Why can a static member function not be virtual?
- How would you register a member function as a callback with a C library that only accepts a function pointer and a void*?
- What is the static initialisation order problem, and how does a function-local static avoid it?
Are you familiar with the operation of a tabletting machine or similar industrial equipment?
Medium · Domain Expertise & Systems
Approach
- Be honest about how much you have used one, then show you understand the machine anyway. A rotary tablet press turns a turret of upper and lower punches over a die table. Granules flow from the feed frame into the dies, the lower punch height sets the fill (and so the tablet weight), pre-compression rollers drive out air, main compression rollers form the tablet, an ejection cam lifts the lower punch, and a take-off scraper sends the tablet to the discharge chute.
- Name the variables that matter: tablet weight, thickness and hardness, turret speed, and pre-compression and main compression force, measured by strain gauges or load cells at the rollers.
- Explain the control loop in software terms. At a fixed punch gap, compression force tracks fill weight, so a controller can watch force per punch, adjust fill depth to keep weight in tolerance, and drive a reject gate for single tablets outside the force limits.
- Lay out the software around it: a PLC for the sequencing and safety interlocks (guard doors, emergency stop), an HMI for operators, alarms and recipe management, and batch records with an audit trail. In regulated pharmaceutical production, those records fall under rules such as 21 CFR Part 11 in the US.
- If you have used other equipment instead, map it onto the same structure (sensors, actuators, control loop, interlocks, data logging) and give one concrete fault you diagnosed.
Follow-up
- How would you detect and reject one out-of-tolerance tablet at full turret speed?
- What would you log per batch so that a quality reviewer can trace a bad tablet back to its cause?
- How would you test the control logic without a physical press, for example with a simulator or hardware-in-the-loop?
What types of bearings are typically used for specific load-bearing requirements?
Medium · Domain Expertise & Systems
Approach
- Start with the split between rolling-element bearings (balls or rollers: low friction and well suited to speed) and plain or sleeve bearings (a lubricant film: compact, tolerant of shock load, common at low speed or under heavy oscillating load).
- Match type to load: deep groove ball bearings for mainly radial load with moderate axial load in both directions at high speed; angular contact ball bearings for combined load with axial load in one direction (mounted in pairs for both); cylindrical roller bearings for heavy radial load with little axial capacity; tapered roller bearings for heavy combined radial and axial load (paired, as in wheel hubs).
- Cover the special cases: spherical roller bearings for heavy load with shaft misalignment or deflection, thrust ball or thrust roller bearings for pure axial load, and needle roller bearings for high radial load where radial space is tight.
- Give the sizing formula: basic rating life L10 = (C/P)^p million revolutions, with p = 3 for ball bearings and 10/3 for roller bearings, where C is the dynamic load rating and P the equivalent load. Doubling the load on a ball bearing cuts its rated life to one eighth.
- Tie it back to software: condition monitoring takes vibration data, runs an FFT and looks for energy at the bearing defect frequencies (BPFO, BPFI, ball spin frequency, cage frequency), alongside temperature trends, to flag wear before failure.
Follow-up
- A shaft sees heavy radial load and some misalignment. Which bearing would you choose, and why?
- What happens to rated life when the equivalent load doubles on a roller bearing?
- How would you build an alert that detects a developing outer-race defect from accelerometer data?
Can you explain the different categories of welding and their applications?
Medium · Domain Expertise & Systems
Approach
- Organise by category. Fusion welding melts the base metal: the arc processes (SMAW/stick, GMAW/MIG, GTAW/TIG, flux-cored, submerged arc), oxy-fuel gas welding, and high-energy beam welding (laser, electron beam). Resistance welding: spot and seam. Solid-state welding joins without melting the base metal: friction, friction stir, ultrasonic, diffusion.
- Match each to an application: TIG for thin stainless steel and aluminium where precision and appearance matter (orbital TIG for hygienic tubing), MIG for high deposition rate and robotic cells, stick for outdoor and field repair, submerged arc for thick plate and long seams, resistance spot welding for sheet-metal bodies, laser for fast, low-distortion precision joints, friction stir for aluminium, and ultrasonic for thermoplastics and thin foils or wires.
- Separate welding from brazing and soldering: those melt only a filler metal (brazing above about 450 °C, soldering below it), never the base metal.
- Name the quality issues: heat-affected zone, distortion, and defects such as porosity, lack of fusion, cracking and undercut. Arc heat input is roughly (V × I × 60) / (1000 × travel speed in mm/min) kJ/mm, times an arc-efficiency factor for the process.
- Tie it to software: in an automated cell, the controller sets and logs current, voltage, wire feed speed and travel speed per seam, alarms on drift, and keeps that record so a failed weld can be traced back to its parameters.
Follow-up
- For thin stainless sheet, what is the trade-off between TIG and MIG?
- How would you monitor weld quality in a robotic cell without destructive testing?
- What is the heat-affected zone, and why does heat input matter for it?
How do you approach Interprocess Communication (IPC) in a real-time system?
Medium · Domain Expertise & Systems
Approach
- Define the constraint first: real-time means bounded worst-case latency, with deadlines that are hard or soft. Judge each IPC mechanism on determinism and worst-case latency, not on average throughput.
- Compare the mechanisms: shared memory with a lock-free single-producer/single-consumer ring buffer (no copy, lowest latency, needs acquire/release memory ordering), POSIX message queues (bounded, prioritised messages through mq_send/mq_receive), pipes and Unix domain sockets (simple, but kernel copies on each message), RTOS queues, and synchronous message passing in systems such as QNX.
- Make it deterministic: preallocate every buffer, keep heap allocation and unbounded blocking out of the real-time path, run the real-time threads under SCHED_FIFO, call mlockall to avoid page faults, and use priority-inheritance, process-shared mutexes (PTHREAD_PRIO_INHERIT with PTHREAD_PROCESS_SHARED) wherever a lock is unavoidable.
- Define the data contract and failure handling: fixed-size messages carrying a sequence number and timestamp so drops and stale data are detectable, a bounded queue with a stated overflow policy (drop oldest, drop newest or apply backpressure), and a heartbeat or watchdog so each side notices when its peer dies.
- Verify with measurements: record a latency histogram under realistic load and report the worst case, not the mean. On Linux with PREEMPT_RT, cyclictest gives you the scheduling-latency baseline.
Follow-up
- What memory-ordering guarantees does a lock-free SPSC ring buffer need between two cores, and where do they go?
- What happens if a process dies while holding a mutex in shared memory, and how do robust mutexes (EOWNERDEAD) help?
- In a control loop, when is it better to drop the oldest message than to block the producer?
Read latency spikes on a sixty-second sawtooth
Medium · cache stampede · single-flight · ttl
The cached listing read path serves about 14k reads/second at an 85% hit rate. p99 sits at 35 ms for 57 seconds, jumps to 900 ms for 3, and repeats. During each spike the primary shows several hundred identical listing queries starting within the same millisecond, all carrying one large tenant's id. Cache entries use a 60-second TTL. Give the mechanism, the ordered checks, the fix, and the correctness hazard your fix must not introduce.
Approach
- Match the period to a configured number before theorising about load. A spike every 60 seconds against a 60-second TTL is an entry expiring, and you confirm it by correlating spike timestamps with the entry's write time rather than with the traffic curve. If the period had matched a cron or a GC interval instead, this is a different investigation.
- Establish the concurrency of the miss. Several hundred identical queries in one millisecond means the miss path has no coalescing: every request that arrives between expiry and repopulation recomputes. The herd size is that key's arrival rate times its recompute time, so at 1.2k reads/second for the hot key and a 250 ms recompute you expect about 300 concurrent misses, which matches what is observed.
- Add single-flight on the miss path so one caller per key recomputes under a short-lived lock while the rest wait for its result. Prefer stale-while-revalidate where the read tolerates it: return the expired value immediately and refresh asynchronously, which removes the latency spike rather than serialising it into a queue of waiters.
- De-synchronise the keys. Write TTLs with jitter, for example 60 seconds plus or minus 10%, so a deploy or a mass invalidation does not align every key on the same second and turn a per-key herd into a fleet-wide one.
Follow-up
- The same sawtooth appears on a key that is invalidated on write rather than expired. Is that the same bug?
- How does your answer change if the recompute takes 4 seconds instead of 250 ms?
Suggested practice checklist
Each day targets one cluster from the reported rounds: memory, concurrency, class design, real-time IPC with the equipment questions, then the testing and stakeholder stories, ending in a full mock.
Day 1 — Map the four rounds and inventory your projects
- Write out the reported sequence (Initial Screening, Technical Rounds, Interactions with Leads, Deep-Dive Discussions) and note which preparation each stage needs from this guide.
- List every C++, embedded, hardware-facing or testing project on your CV with one fact each: your role, the technology, one outcome.
- Do a short diagnostic: explain out loud what happens when you call new on a class with a constructor, and note what you could not say.
Deliverable: A one-page loop map, a project inventory with one fact per project, and a list of the fundamentals that failed the diagnostic.
Day 2 — C++ memory: malloc, calloc and new
- Write a program that allocates a class with a non-trivial constructor and destructor using malloc, calloc and new, and print when each runs.
- Add the failure cases: new throwing std::bad_alloc, new(std::nothrow) returning nullptr, malloc returning NULL, calloc's overflow-checked count times size.
- Introduce deliberate mismatches (free on new memory, delete on new[] memory) and run under AddressSanitizer or valgrind to see the reports.
- Write one paragraph on placement new and when you would use it.
Deliverable: A runnable source file, the sanitizer output for the mismatches and a six-row comparison table of malloc, calloc and new.
Day 3 — Processes, threads, mutexes and semaphores
- Write a table of what a process and a thread share or keep private (address space, heap, file descriptors, stack, registers), plus context-switch cost and what a crash does to each.
- Implement a bounded producer-consumer queue with std::mutex and std::condition_variable, then re-implement it with counting semaphores.
- Create a two-lock deadlock, then fix it by fixed lock ordering or std::scoped_lock, and write down why a semaphore cannot enforce ownership the way a mutex does.
Deliverable: The comparison table, both queue implementations, and a paragraph explaining mutex versus semaphore with the deadlock example.
Day 4 — Private constructors and static member functions
- Implement three uses of a private constructor: a static factory returning std::unique_ptr, a function-local-static singleton, and a class that cannot be instantiated at all.
- Write a class with static data and static member functions, and demonstrate the limits: no this pointer, cannot be const or virtual, callable without an object.
- Use a static member function as a C-style callback or thread entry point and explain why a non-static member cannot be passed that way.
Deliverable: One source file with the three constructor patterns and the static-member demos, plus a one-paragraph answer to each question that you can say aloud.
Day 5 — Real-time IPC and the industrial-equipment questions
- Compare pipes, POSIX message queues, shared memory with semaphores and sockets on copy cost, blocking and bounded latency, and pick one for a sensor-to-controller link with a justification.
- Write down how priority inversion happens, how priority inheritance fixes it, and what to avoid in a real-time hot path (dynamic allocation, unbounded waits, page faults).
- Write half a page each on how a tablet press works (powder, die, upper and lower punches), bearing types for radial, axial and combined loads, and the main welding categories.
- For each domain topic, add one sentence on what the software would need to monitor or control.
Deliverable: An IPC decision table, a priority-inversion note and three half-page domain sheets, each marking firsthand versus read knowledge.
Day 6 — Testing story, stakeholder explanation and career intent
- Write the testing-frameworks answer as a story: project, tools, layers, what you stubbed, a defect caught, a lesson learned.
- Prepare an explanation of one technical issue for a project manager, covering impact, risk and fix without jargon.
- Write a two-year goal and a why-this-team answer, then work through the practice questions on a code-review disagreement and on unblocking an engineer for conflict and teamwork stories.
Deliverable: Three written stories (testing, stakeholder explanation, conflict) and a two-sentence career-intent answer.
Day 7 — Full mock and deep-dive rehearsal
- Run a mock that starts with the screening pitch, then fundamentals, then the domain questions, then the behavioral stories, answering each aloud in order.
- Rehearse the code-optimisation story as measure, change, re-measure, using the practice question on latency spikes to drill the measure-first habit.
- Choose two questions to ask leadership about software-hardware responsibilities, and review notes on every claim you plan to defend.
Deliverable: A recorded or written mock with the weakest answers marked for repair, and two questions for leadership.
Stakeholder, testing and career-intent stories
Candidates report that the later conversations cover conflict over technical direction, adaptability to new tools and projects, and career intent, and the testing-experience question sits in the same tier. For this role, prepare stories where hardware, testing or a non-technical audience is involved, and make sure each shows a decision you owned and its outcome.
- Describe a time you explained a complex technical issue to someone outside engineering.
- Walk through an occasion when you tuned code so a system ran faster on its hardware.
- Where do you want to be in two years, and how does this role get you there?
- Walk through the life cycle of a process in a system you built or maintained.
- Describe a disagreement about technical direction and how it was resolved.
What is your experience with testing frameworks and automated testing tools?
Medium · Domain Expertise & Systems
Approach
- Open with one project and name the tools you really used, then say which layer each covered. For C++ that might be GoogleTest or Catch2 for unit tests, CTest or CI for runs, and a separate harness for integration against real or simulated devices. A tool list without layers sounds like keywords.
- Show a decision that was yours: why you picked that framework, what you replaced with a fake or stub (for example a hardware interface behind an abstract class so the control logic runs without the machine), and what stayed manual and why.
- Give a result you can defend: a defect the tests caught before release, a regression suite that made a refactor safe, or a gap you closed. Quote numbers only if they are real; an invented coverage figure falls apart at the first follow-up.
- Cover the hard cases: timing-dependent and flaky tests (fake clocks, no sleeps, repeated runs), concurrency bugs (run under ThreadSanitizer or AddressSanitizer) and hardware-dependent behavior (simulation first, then a bench run).
- If your automated testing experience is limited, say so and describe the smallest harness you wrote, what it caught and how you would extend it. Do not claim breadth you cannot back up.
Follow-up
- How did you test code that depends on hardware you did not have on your desk?
- A test passes locally but fails intermittently in CI. How do you find the cause?
- How do you decide what to automate and what to leave to manual testing?
Turn a code review disagreement into a decision
Easy · code review · optimistic concurrency · communication
A colleague's change updates a row with UPDATE resource SET version = version + 1 WHERE resource_id = $1 AND version = $2 and treats an affected-row count of zero as a successful no-op. You read that as a silently lost update; they think returning 200 is friendlier to clients than returning a conflict. Describe how you have handled a review disagreement of this shape: what goes in the comment, when you leave the thread, and who decides. Then write the comment you would leave here, in under 80 words.
Approach
- Sort the disagreement before writing anything. A silently discarded write is a correctness claim about data; the choice between 409 and 412 is taste. Only the first justifies blocking a merge, and saying which one you are doing is most of the value of the comment.
- Make the claim reproducible in the comment itself with an interleaving rather than a principle: A reads version 7, B reads version 7, B commits version 8, A's predicate matches zero rows, A is told it succeeded and A's edit is gone.
- Offer the alternative with its cost attached: return 409 carrying the current version and the revision that won, so the client can re-read and re-apply. Note that automatic retry is not the fix, because a retry re-reads the winner's state and reapplies an intent formed against data that no longer exists.
- Apply an escalation rule you can state: two round trips on the thread, then a call, and the service's owner decides rather than the reviewer. A reviewer who cannot be overruled is a bottleneck with extra steps.
Follow-up
- Where would you put the test that fails if someone reintroduces the swallowed zero rowcount?
- The author says clients cannot handle a 409. How do you check whether that is true?
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?