C++ Code Reading: Bug in a Linked-List insertAfter and Predicting Polymorphic Output
Company: AMD
Role: Software Engineer
Category: Software Engineering Fundamentals
Difficulty: medium
Interview Round: Onsite
This round handed out short C++ programs and asked you to read them: find a bug, predict the exact output, and explain why. The exact snippets used in the interview were not shared. The two programs below are representative reconstructions: the first has the defect described in the round (a singly linked list `insertAfter` function missing its key pointer update), and the second exercises the polymorphism and overriding concepts the round covered.
### Clarifying Questions
- Should the predicted output be exact, including spacing and line breaks?
- May I assume a standard-conforming C++17 compiler with default settings?
- Besides the output, should I fix the code and point out other problems, such as memory leaks?
### Part 1 — The buggy `insertAfter`
`insertAfter(prev, value)` is supposed to insert a new node holding `value` immediately after `prev`. The comments in `main` show the list each call is meant to produce. Find the bug, predict exactly what the program prints, and fix it.
```cpp
#include <iostream>
struct Node {
int value;
Node* next;
Node(int v) : value(v), next(nullptr) {}
};
void insertAfter(Node* prev, int value) {
if (prev == nullptr) return;
Node* node = new Node(value);
prev->next = node;
}
void print(const Node* head) {
for (const Node* cur = head; cur != nullptr; cur = cur->next) {
std::cout << cur->value << (cur->next ? " -> " : "\n");
}
}
int main() {
Node* head = new Node(1);
insertAfter(head, 3); // intended list: 1 -> 3
insertAfter(head, 2); // intended list: 1 -> 2 -> 3
insertAfter(head->next, 4); // intended list: 1 -> 2 -> 4 -> 3
print(head);
}
```
```hint Draw every pointer
Draw the list after each call, including where every node's `next` points, and compare it with the intended list in the comments. Ask what happens to the node that used to follow `prev`.
```
#### What This Part Should Cover
- The exact output, backed by a step-by-step trace
- The missing assignment and its consequences for the list and for memory
- A corrected function, including why the order of the two assignments matters
- Edge cases: a null `prev`, inserting after the tail, and cleanup of the list
### Part 2 — Predict the polymorphic output
Predict exactly what this program prints. For every line of output, explain why it is printed.
```cpp
#include <iostream>
class Base {
public:
Base() { hello(); }
virtual ~Base() = default;
virtual void hello() const { std::cout << "Base::hello\n"; }
void greet() const { std::cout << "Base::greet\n"; }
virtual void show(int x = 1) const { std::cout << "Base::show " << x << "\n"; }
};
class Derived : public Base {
public:
Derived() { hello(); }
void hello() const override { std::cout << "Derived::hello\n"; }
void greet() const { std::cout << "Derived::greet\n"; }
void show(int x = 2) const override { std::cout << "Derived::show " << x << "\n"; }
};
void byValue(Base b) { b.hello(); }
int main() {
Derived d;
Base* p = &d;
p->hello();
p->greet();
p->show();
d.show();
byValue(d);
}
```
```hint Which type decides
For each call, note the static type of the expression, the dynamic type of the object at that moment, and whether the function is virtual.
```
```hint Special rules
Constructors, default arguments and pass-by-value each follow their own rule about which type is used. Check each of those calls separately.
```
#### What This Part Should Cover
- The exact output, line by line
- Virtual dispatch versus non-virtual name hiding
- The rules for virtual calls during construction, default arguments on virtual functions, and passing a derived object by value
- How language features such as `override`, `final` and references help avoid these surprises
### What a Strong Answer Covers
- Precise traces instead of guesses, with the exact predicted output
- The underlying language rule named for every surprising line
- Correct fixes, with attention to memory ownership and leaks
- Awareness of what is defined behavior and what is not
### Follow-up Questions
- In Part 1, what would happen if you wrote the two pointer assignments in the opposite order?
- In Part 2, what happens if `Base::hello` is made pure virtual and is still called from the `Base` constructor?
- If `Derived::greet` were declared with `override`, what would the compiler do?
- How would you change `byValue` so that it calls `Derived::hello`?
Overview: Two C++ code-reading exercises: find the bug in a singly linked list insertAfter function that is missing a pointer update and predict what the program prints, then predict and explain the output of code mixing virtual overriding, name hiding, constructors, default arguments and pass-by-value. It tests precise tracing of pointers and dispatch rules.
Read the full AMD Software Engineer interview experience this question came from