Introduction

Converting a binary tree or binary search tree (BST) into a doubly linked list (DLL) is a standard programming and interview problem. In this process, we reuse the tree’s left and right pointers: the left becomes the prev pointer, and the right becomes the next pointer in the doubly linked list. The order of elements in the final list typically follows an inorder traversal (i.e., left → root → right).

Why is this useful?

Problem Statement - What exactly do we want

Let’s restate in plain words:

So the goal is: “convert binary tree to doubly linked list in C++ in place”, preserving inorder order.

Core idea — Why inorder traversal helps

When you traverse a binary tree in inorder (left, root, right), you exactly visit nodes in the order you want in the final doubly linked list. So the strategy is:

  1. Traverse the left subtree

  2. Visit the current node → link it with the previously visited node

  3. Traverse the right subtree

We maintain a pointer prev that always points to the last node we processed in the DLL. When we reach a new node curr, we do:

If prev is nullptr (i.e. first visited node), we mark this curr as the head of the DLL.

Thus, as we traverse, we gradually “chain” nodes into a doubly linked list.

This is the standard method explained in many algorithm resources.

Main approaches (with pros & cons)

Here are three common ways to implement tree → DLL conversion in C++:

1. Recursive Inorder Approach (simplest)

How it works

Time & Space

Full C++ code example

#include <iostream>
using namespace std;

struct Node {
    int val;
    Node *left, *right;
    Node(int x) : val(x), left(nullptr), right(nullptr) {}
};

Node* prevPtr = nullptr;  // tracks last processed node
Node* headDLL = nullptr;  // head of final doubly linked list

void inorderConvert(Node* root) {
    if (root == nullptr) return;
    inorderConvert(root->left);

    // now “visit” this node
    if (prevPtr == nullptr) {
        // this is the first (leftmost) node
        headDLL = root;
    } else {
        prevPtr->right = root;
        root->left = prevPtr;
    }
    prevPtr = root;

    inorderConvert(root->right);
}

void printDLL(Node* head) {
    Node* cur = head;
    while (cur) {
        cout << cur->val;
        if (cur->right) cout << " <-> ";
        cur = cur->right;
    }
    cout << "\n";
}

int main() {
    // Example tree:
    Node* root = new Node(10);
    root->left = new Node(5);
    root->right = new Node(20);
    root->right->left = new Node(15);

    inorderConvert(root);
    printDLL(headDLL);
    // Output: 5 <-> 10 <-> 15 <-> 20

    return 0;
}

Why this approach is good

Limitations

2. Iterative Approach (using explicit stack)

Idea

Pseudo/sketch

  1. Create an empty stack

  2. Use a pointer curr = root

  3. While (curr != nullptr or stack not empty):
      a. Go deep: while curr != nullptr, push curr and move curr = curr->left
      b. Pop node from stack → call it node
      c. Visit/link it: prev->right = node, node->left = prev, etc.
      d. Set prev = node
      e. Move curr = node->right

Pros & cons

I can provide a full C++ version on request.

3. Morris Traversal / Threaded Approach (O(1) extra space)

Idea

High-level sketch

Node* curr = root;
Node* prev = nullptr;
Node* head = nullptr;

while (curr) {
    if (curr->left == nullptr) {
        // visit curr
        if (!prev) head = curr;
        else {
            prev->right = curr;
            curr->left = prev;
        }
        prev = curr;
        curr = curr->right;
    } else {
        Node* pred = curr->left;
        while (pred->right && pred->right != curr) {
            pred = pred->right;
        }
        if (pred->right == nullptr) {
            pred->right = curr;  // make thread
            curr = curr->left;
        } else {
            pred->right = nullptr;  // remove thread
            // visit curr
            if (!prev) head = curr;
            else {
                prev->right = curr;
                curr->left = prev;
            }
            prev = curr;
            curr = curr->right;
        }
    }
}

Pros & cons

Use Morris only when memory is strictly constrained and you are comfortable with manipulating tree pointers.

Which method to choose (for everyday coding)

Edge Cases, Pitfalls & Debugging Tips

Full Example - Recursive + Iterative (combined)

Below is a combined C++ program that includes both recursive and iterative versions, so you can test both:

#include <iostream>
#include <stack>
using namespace std;

struct Node {
    int val;
    Node *left, *right;
    Node(int x) : val(x), left(nullptr), right(nullptr) {}
};

// ============ Recursive method ============
Node* recPrev = nullptr;
Node* recHead = nullptr;

void inorderRec(Node* root) {
    if (!root) return;
    inorderRec(root->left);
    if (!recPrev) {
        recHead = root;
    } else {
        recPrev->right = root;
        root->left = recPrev;
    }
    recPrev = root;
    inorderRec(root->right);
}

// ============ Iterative method ============
Node* convertIterative(Node* root) {
    if (!root) return nullptr;
    stack<Node*> st;
    Node* curr = root;
    Node* prev = nullptr;
    Node* head = nullptr;

    while (curr || !st.empty()) {
        while (curr) {
            st.push(curr);
            curr = curr->left;
        }
        curr = st.top();
        st.pop();
        if (!prev) {
            head = curr;
        } else {
            prev->right = curr;
            curr->left = prev;
        }
        prev = curr;
        curr = curr->right;
    }
    return head;
}

// Print DLL
void printDLL(Node* head) {
    Node* cur = head;
    while (cur) {
        cout << cur->val;
        if (cur->right) cout << " <-> ";
        cur = cur->right;
    }
    cout << "\n";
}

int main() {
    // Example tree
    Node* root = new Node(10);
    root->left = new Node(5);
    root->right = new Node(20);
    root->right->left = new Node(15);

    // Recursive version
    inorderRec(root);
    cout << "Recursive conversion gives: ";
    printDLL(recHead);

    // Reset globals (not strictly necessary for this example)
    recPrev = nullptr; recHead = nullptr;

    // Iterative version
    Node* head2 = convertIterative(root);
    cout << "Iterative conversion gives: ";
    printDLL(head2);

    return 0;
}

Summary