C++ Code Reading: Bug in a Linked-List insertAfter and Predicting Polymorphic Output

Read the full interview experience this question came from →

Quick 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.

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

|Home/Software Engineering Fundamentals/AMD
AMD logo
AMD
Oct 9, 2026
mediumSoftware EngineerOnsiteSoftware Engineering Fundamentals
0
0

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 Guidance

  • 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.

#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);
}

What This Part Should Cover Guidance

  • 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.

#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);
}

What This Part Should Cover Guidance

  • 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 Guidance

  • 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 Guidance

  • 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 ?
Loading comments...