Refactor a Correct but Quadratic C++ Routine Under a Microsecond Budget
Company: Citadel
Role: Software Engineer
Category: Software Engineering Fundamentals
Difficulty: medium
Interview Round: Online Assessment
# Refactor a Correct but Quadratic C++ Routine Under a Microsecond Budget
You are given a logically correct C++ function named `root_node`, together with representative large inputs. The implementation is much too slow: it contains nested scans, repeated `std::find_if` and `std::advance` calls, `std::pow` for squaring, complex bit shifts, `std::sqrt`, and avoidable vector copies.
Explain how you would refactor the supplied implementation toward `O(N)` time while preserving its exact output. The target is a measured execution time below 100 microseconds for the agreed benchmark inputs.
### Constraints & Assumptions
- The actual source code and benchmark data are provided during the exercise; do not infer missing `root_node` semantics.
- Output equivalence includes boundary inputs, integer overflow behavior, and any ordering or tie behavior in the original contract.
- The 100-microsecond target is meaningful only after hardware, compiler, flags, warm-up, input distribution, and measurement method are fixed.
- Optimization must be justified by measurement and complexity analysis, not by replacing expressions mechanically.
### Clarifying Questions to Ask
- What are the maximum input size and value ranges, and which inputs define the latency requirement?
- Is allocation included in the benchmark, and may the caller reuse scratch storage or a precomputed index?
- Which compiler, optimization flags, processor, and timing percentile define 100 microseconds?
- Do integer overflow and floating-point rounding have specified behavior that the refactor must preserve?
- May the data representation or function signature change, or only the implementation body?
### What a Strong Answer Covers
- Profiling that identifies the actual hot path and separates algorithmic work, allocation, cache misses, and benchmark noise.
- A derivation that replaces repeated linear searches or iterator advances with a one-pass index, lookup structure, or maintained state appropriate to the supplied code.
- Careful algebraic treatment of `std::pow`, shifts, and `std::sqrt`, including domains where an apparently equivalent integer comparison can overflow or change rounding.
- Passing vectors by `const` reference where ownership is unnecessary and eliminating temporary allocations inside the measured path.
- A differential test strategy comparing original and optimized outputs across boundary, randomized, and adversarial inputs before trusting speed results.
- A reproducible microbenchmark with optimized builds, warm-up, enough repetitions, percentile reporting, and protection against dead-code elimination.
- A candid response if `O(N)` is achieved but the fixed benchmark still misses 100 microseconds.
### Follow-up Questions
1. When is replacing `std::pow(x, 2)` with `x * x` unsafe even though it is faster?
2. How can you remove a `std::sqrt` comparison without changing behavior at large integer values?
3. What evidence would show that an `unordered_map` removed quadratic work but made the measured input slower?
4. How do you prove that a faster implementation preserves the original tie or ordering behavior?
5. What would you report if the 100-microsecond result holds only for the median and not the required percentile?
Overview: Refactor a correct C++ routine from repeated quadratic scans toward linear time while preserving exact behavior. The discussion covers profiling, one-pass indexing, algebraic replacements for costly math, copy avoidance, differential testing, and a reproducible 100-microsecond benchmark.
Read the full Citadel Software Engineer interview experience this question came from