<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/scripts/pretty-feed-v3.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:h="http://www.w3.org/TR/html4/"><channel><title>Chillymosh&apos;s Blog</title><description>A blog of ramblings by Chillymosh</description><link>https://chillymosh.com</link><item><title>Binary Search Trees: Fast Lookups and Dynamic Data</title><link>https://chillymosh.com/blog/binary-search-trees</link><guid isPermaLink="true">https://chillymosh.com/blog/binary-search-trees</guid><description>Learn how Binary Search Trees enable fast search, insert, and delete, plus pitfalls and why balancing is key for performance.</description><pubDate>Sun, 03 Aug 2025 07:10:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside, Collapse, MdxRepl, Tabs, TabItem } from &quot;astro-pure/user&quot;
import BSTExample from &quot;binarytree-BST.svg&quot;;
import BSTExample2 from &quot;binarytree-BST2.svg&quot;;
import NodeCycler from &apos;@/components/NodeCycler2.astro&apos;;&lt;/p&gt;
&lt;p&gt;Binary Search Trees (BSTs) are one of the most important and widely used types of binary trees. By maintaining a sorted structure, BSTs provide efficient searching, insertion, and deletion operations at the heart of sets, maps, databases, and more.&lt;/p&gt;
&lt;p&gt;In this post, we&apos;ll cover:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What makes a tree a BST&lt;/li&gt;
&lt;li&gt;Search, insertion, and deletion algorithms&lt;/li&gt;
&lt;li&gt;Average vs worst-case performance&lt;/li&gt;
&lt;li&gt;Common pitfalls and balancing needs&lt;/li&gt;
&lt;li&gt;Practical code examples&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;What is a Binary Search Tree?&lt;/h2&gt;
&lt;p&gt;A Binary Search Tree (also called an ordered or sorted binary tree) is a tree-based data structure where each node follows a strict organising rule: its value is greater than everything in its left subtree and less than everything in its right subtree.
This ordering keeps the tree&apos;s data sorted as you insert or remove nodes, which enables fast searching, insertion, and deletion. This is often much more efficient than a simple list.&lt;/p&gt;
&lt;p&gt;Imagine a perfectly organised filing system where every bit of data knows exactly where it belongs. The performance of searching, inserting, or deleting all hinges on the tree&apos;s height.
Shorter, well-balanced trees deliver the best performance.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Left side: All values smaller than the current node&lt;/li&gt;
&lt;li&gt;Right side: All values larger than the current node&lt;/li&gt;
&lt;li&gt;Universal rule: This pattern holds true at every single node in the tree&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This simple principle makes sure your data stays sorted, which makes searching incredibly fast.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Search&lt;/h2&gt;
&lt;p&gt;How to efficiently find a value in a BST:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Start at the root.&lt;/li&gt;
&lt;li&gt;If the value equals the root, you&apos;re done.&lt;/li&gt;
&lt;li&gt;If less, search the left subtree; if greater, search the right.&lt;/li&gt;
&lt;li&gt;Repeat until found or you hit a leaf. If the latter then you would return None.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;#x3C;NodeCycler
SvgComponent={BSTExample}
nodeOrder={[&quot;15&quot;, &quot;19&quot;, &quot;21&quot;, &quot;20&quot;]}
interval={600}
/&gt;&lt;/p&gt;
&lt;p&gt;The time complexity for searching a BST for a value is &lt;strong&gt;O(h)&lt;/strong&gt; where &lt;strong&gt;h&lt;/strong&gt; is the height of the tree.
If the tree is unbalanced then the height of the tree often becomes larger than it needs to be, which will cause searches to take longer.&lt;/p&gt;
&lt;h3&gt;Recursive&lt;/h3&gt;
&lt;h3&gt;Iterative&lt;/h3&gt;
&lt;hr&gt;
&lt;h2&gt;Insert&lt;/h2&gt;
&lt;p&gt;How to insert a new value while maintaining the BST property:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Start at the root.&lt;/li&gt;
&lt;li&gt;If less, go left; if greater, go right.&lt;/li&gt;
&lt;li&gt;Repeat until you find a None/null spot.&lt;/li&gt;
&lt;li&gt;Insert the new node there.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;#x3C;NodeCycler
SvgComponent={BSTExample2}
nodeOrder={[&quot;15&quot;, &quot;7&quot;, &quot;4&quot;, &quot;5&quot;]}
interval={600}
insertNode={{ id: &quot;5&quot;, afterStep: 3 }}
insertEdge={{ id: &quot;NedKu-IQ1OCdtTVTFXyA-21&quot;, afterStep: 3 }}
/&gt;&lt;/p&gt;
&lt;h3&gt;Recursive&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;if (data &amp;#x3C; node.val) {
    node.left = insert(node.left, data);
} else if (data &gt; node.val) {
    node.right = insert(node.right, data);
}

return node;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;#x3C;/TabItem&gt;
&amp;#x3C;TabItem label=&quot;C#&quot;&gt;
```c#
public Node Insert(Node node, int data)
{
    if (node == null)
    {
        return new Node(data);
    }

    if (data &amp;#x3C; node.val)
    {
        node.left = Insert(node.left, data);
    }
    else if (data &gt; node.val)
    {
        node.right = Insert(node.right, data);
    }

    return node;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;if (data &amp;#x3C; node-&gt;val) {
    node-&gt;left = insert(node-&gt;left, data);
} else if (data &gt; node-&gt;val) {
    node-&gt;right = insert(node-&gt;right, data);
}

return node;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;#x3C;/TabItem&gt;
&amp;#x3C;TabItem label=&quot;C&quot;&gt;
```c
Node* insert(Node* node, int key) {
    if (node == NULL) {
        Node* new_node = (Node*)malloc(sizeof(Node));
        new_node-&gt;val = key;
        new_node-&gt;left = new_node-&gt;right = NULL;
        return new_node;
    }

    if (key &amp;#x3C; node-&gt;val) {
        node-&gt;left = insert(node-&gt;left, key);
    } else if (key &gt; node-&gt;val) {
        node-&gt;right = insert(node-&gt;right, key);
    }

    return node;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Iterative&lt;/h3&gt;
&lt;p&gt;Iterative BST insertion is possible but less common in practice. Recursive methods are clearer for most cases.&lt;/p&gt;
&lt;p&gt;Iteration is used when the application is performance critical or the Binary Tree is deep enough it could cause a stack overflow.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;current = root
while True:
    if data &amp;#x3C; current.val:
        if current.left is None:
            current.left = Node(data)
            break
        else:
            current = current.left
    elif data &gt; current.val:
        if current.right is None:
            current.right = Node(data)
            break
        else:
            current = current.right
    else:
        break 

return root
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;&amp;#x3C;/TabItem&gt;
&amp;#x3C;TabItem label=&quot;Javascript&quot;&gt;
```js
function insert(root, val) {
    if (root === null) {
        return new Node(val);
    }
    
    let current = root;
    while (true) {
        if (val &amp;#x3C; current.val) {
            if (current.left === null) {
                current.left = new Node(val);
                break;
            }
            current = current.left;
        } else if (val &gt; current.val) {
            if (current.right === null) {
                current.right = new Node(val);
                break;
            }
            current = current.right;
        } else {
            break;
        }
    }
    return root;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;current := root
for {
    if val &amp;#x3C; current.Val {
        if current.Left == nil {
            current.Left = &amp;#x26;Node{Val: val}
            break
        }
        current = current.Left
    } else if val &gt; current.Val {
        if current.Right == nil {
            current.Right = &amp;#x26;Node{Val: val}
            break
        }
        current = current.Right
    } else {
        break
    }
}
return root
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;#x3C;/TabItem&gt;
&amp;#x3C;TabItem label=&quot;Java&quot;&gt;
```java
public Node insert(Node root, int val) {
    if (root == null) {
        return new Node(val);
    }
    
    Node current = root;
    while (true) {
        if (val &amp;#x3C; current.val) {
            if (current.left == null) {
                current.left = new Node(val);
                break;
            }
            current = current.left;
        } else if (val &gt; current.val) {
            if (current.right == null) {
                current.right = new Node(val);
                break;
            }
            current = current.right;
        } else {
            break;
        }
    }
    return root;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Node current = root;
while (true) {
    if (val &amp;#x3C; current.val) {
        if (current.left == null) {
            current.left = new Node(val);
            break;
        }
        current = current.left;
    } else if (val &gt; current.val) {
        if (current.right == null) {
            current.right = new Node(val);
            break;
        }
        current = current.right;
    } else {
        break;
    }
}
return root;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;#x3C;/TabItem&gt;
&amp;#x3C;TabItem label=&quot;C++&quot;&gt;
```cpp
Node* insert(Node* root, int val) {
    if (root == nullptr) {
        return new Node(val);
    }
    
    Node* current = root;
    while (true) {
        if (val &amp;#x3C; current-&gt;val) {
            if (current-&gt;left == nullptr) {
                current-&gt;left = new Node(val);
                break;
            }
            current = current-&gt;left;
        } else if (val &gt; current-&gt;val) {
            if (current-&gt;right == nullptr) {
                current-&gt;right = new Node(val);
                break;
            }
            current = current-&gt;right;
        } else {
            break;
        }
    }
    return root;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Node* current = root;
while (1) {
    if (val &amp;#x3C; current-&gt;val) {
        if (current-&gt;left == NULL) {
            current-&gt;left = createNode(val);
            break;
        }
        current = current-&gt;left;
    } else if (val &gt; current-&gt;val) {
        if (current-&gt;right == NULL) {
            current-&gt;right = createNode(val);
            break;
        }
        current = current-&gt;right;
    } else {
        break;
    }
}
return root;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;#x3C;/TabItem&gt;
&amp;#x3C;TabItem label=&quot;Rust&quot;&gt;
```rs
pub fn insert(mut root: Option&amp;#x3C;Box&amp;#x3C;Node&gt;&gt;, val: i32) -&gt; Option&amp;#x3C;Box&amp;#x3C;Node&gt;&gt; {
    let mut curr = &amp;#x26;mut root;
    loop {
        match curr {
            None =&gt; {
                *curr = Some(Box::new(Node::new(val)));
                break;
            }
            Some(node) =&gt; {
                if val &amp;#x3C; node.val {
                    curr = &amp;#x26;mut node.left;
                } else if val &gt; node.val {
                    curr = &amp;#x26;mut node.right;
                } else {
                    break;
                }
            }
        }
    }
    root
}

&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;Delete&lt;/h2&gt;
&lt;p&gt;Deleting a value from a BST is more involved:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If the node is a leaf: remove it.&lt;/li&gt;
&lt;li&gt;If it has one child: replace it with its child.&lt;/li&gt;
&lt;li&gt;If it has two children then you have two options:
&lt;ul&gt;
&lt;li&gt;Replace it with its in-order successor (smallest node in right subtree)&lt;/li&gt;
&lt;li&gt;Replace it with its in-order predecessor (largest node in left subtree)&lt;/li&gt;
&lt;li&gt;After either of the above, remove the target node.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;#x3C;NodeCycler
SvgComponent={BSTExample2}
nodeOrder={[&quot;15&quot;, &quot;7&quot;, &quot;4&quot;, &quot;5&quot;]}
interval={600}
deleteNode={{ id: &quot;5&quot;, afterStep: 4 }}
deleteEdge={{ id: &quot;NedKu-IQ1OCdtTVTFXyA-21&quot;, afterStep: 4 }}
/&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Deleting the root 15&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Find the next in-order successor (18)&lt;/li&gt;
&lt;li&gt;The leaf of 18 is now the root, replacing 15.&lt;/li&gt;
&lt;li&gt;If we wanted the in-order predecessor then the leaf 9 would replace 15.
&amp;#x3C;/ Aside&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Recursive&lt;/h3&gt;
&lt;p&gt;These examples cover every deletion case in a BST and use small, purpose built helpers for clarity.
In real world code you can simplify further.&lt;/p&gt;
&lt;p&gt;e.g. merge &lt;code&gt;find_successor&lt;/code&gt; and &lt;code&gt;find_predecessor&lt;/code&gt; into one function, or even inline that logic directly into your delete function if you don&apos;t need the separate helpers.&lt;/p&gt;
&lt;p&gt;def find_predecessor(node):
current = node.left
while current.right is not None:
current = current.right
return current&lt;/p&gt;
&lt;p&gt;def delete(root, target, use_successor=True):
if root is None:
return None&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if target &amp;#x3C; root.val:
    root.left = delete(root.left, target, use_successor)
elif target &gt; root.val:
    root.right = delete(root.right, target, use_successor)
elif root.left is None and root.right is None:
    return None
elif root.left is None:
    return root.right
elif root.right is None:
    return root.left
else:
    if use_successor:
        replacement = find_successor(root)
        root.val = replacement.val
        root.right = delete(root.right, replacement.val, use_successor)
    else:
        replacement = find_predecessor(root)
        root.val = replacement.val
        root.left = delete(root.left, replacement.val, use_successor)

return root
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;&amp;#x3C;/TabItem&gt;
&amp;#x3C;TabItem label=&quot;Javascript&quot;&gt;
```js
function findSuccessor(node) {
  let current = node.right;
  while (current.left !== null) {
    current = current.left;
  }
  return current;
}

function findPredecessor(node) {
  let current = node.left;
  while (current.right !== null) {
    current = current.right;
  }
  return current;
}

function deleteNode(root, target, useSuccessor = true) {
  if (root === null) {
    return null;
  }

  if (target &amp;#x3C; root.val) {
    root.left = deleteNode(root.left, target, useSuccessor);
    return root;
  }
  if (target &gt; root.val) {
    root.right = deleteNode(root.right, target, useSuccessor);
    return root;
  }

  switch (true) {
    case (root.left === null &amp;#x26;&amp;#x26; root.right === null):
      return null;

    case (root.left === null):
      return root.right;

    case (root.right === null):
      return root.left;

    default:
      if (useSuccessor) {
        const replacement = findSuccessor(root);
        root.val = replacement.val;
        root.right = deleteNode(root.right, replacement.val, true);
      } else {
        const replacement = findPredecessor(root);
        root.val = replacement.val;
        root.left = deleteNode(root.left, replacement.val, false);
      }
      return root;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;func findPredecessor(node *Node) *Node {
current := node.Left
for current.Right != nil {
current = current.Right
}
return current
}&lt;/p&gt;
&lt;p&gt;func deleteNode(root *Node, target int, useSuccessor bool) *Node {
if root == nil {
return nil
}
if target &amp;#x3C; root.Val {
root.Left = deleteNode(root.Left, target, useSuccessor)
} else if target &gt; root.Val {
root.Right = deleteNode(root.Right, target, useSuccessor)
} else {
switch {
case root.Left == nil &amp;#x26;&amp;#x26; root.Right == nil:
return nil
case root.Left == nil:
return root.Right
case root.Right == nil:
return root.Left
default:
if useSuccessor {
repl := findSuccessor(root)
root.Val = repl.Val
root.Right = deleteNode(root.Right, repl.Val, useSuccessor)
} else {
repl := findPredecessor(root)
root.Val = repl.Val
root.Left = deleteNode(root.Left, repl.Val, useSuccessor)
}
}
}
return root
}&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;#x3C;/TabItem&gt;
&amp;#x3C;TabItem label=&quot;Java&quot;&gt;
```java
public Node findSuccessor(Node node) {
    Node current = node.right;
    while (current.left != null) {
        current = current.left;
    }
    return current;
}

public Node findPredecessor(Node node) {
    Node current = node.left;
    while (current.right != null) {
        current = current.right;
    }
    return current;
}

public Node deleteNode(Node root, int target, boolean useSuccessor) {
    if (root == null) {
        return root;
    }
    if (target &amp;#x3C; root.val) {
        root.left = deleteNode(root.left, target, useSuccessor);
    } else if (target &gt; root.val) {
        root.right = deleteNode(root.right, target, useSuccessor);
    } else if (root.left == null &amp;#x26;&amp;#x26; root.right == null) {
        return null;
    } else if (root.left == null) {
        return root.right;
    } else if (root.right == null) {
        return root.left;
    } else {
        if (useSuccessor) {
            Node replacement = findSuccessor(root);
            root.val = replacement.val;
            root.right = deleteNode(root.right, replacement.val, useSuccessor);
        } else {
            Node replacement = findPredecessor(root);
            root.val = replacement.val;
            root.left = deleteNode(root.left, replacement.val, useSuccessor);
        }
    }
    return root;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;public Node FindPredecessor(Node node) {
var current = node.left;
while (current.right != null) {
current = current.right;
}
return current;
}&lt;/p&gt;
&lt;p&gt;public Node DeleteNode(Node root, int target, bool useSuccessor = true) {
if (root == null) {
return root;
}&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (target &amp;#x3C; root.val) {
    root.left = DeleteNode(root.left, target, useSuccessor);
} else if (target &gt; root.val) {
    root.right = DeleteNode(root.right, target, useSuccessor);
} else if (root.left == null &amp;#x26;&amp;#x26; root.right == null) {
    return null;
} else if (root.left == null) {
    return root.right;
} else if (root.right == null) {
    return root.left;
} else {
    if (useSuccessor) {
        var replacement = FindSuccessor(root);
        root.val = replacement.val;
        root.right = DeleteNode(root.right, replacement.val, useSuccessor);
    } else {
        var replacement = FindPredecessor(root);
        root.val = replacement.val;
        root.left = DeleteNode(root.left, replacement.val, useSuccessor);
    }
}
return root;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;#x3C;/TabItem&gt;
&amp;#x3C;TabItem label=&quot;C++&quot;&gt;
```cpp
Node* findSuccessor(Node* node) {
    auto current = node-&gt;right;
    while (current-&gt;left) {
        current = current-&gt;left;
    }
    return current;
}

Node* findPredecessor(Node* node) {
    auto current = node-&gt;left;
    while (current-&gt;right) {
        current = current-&gt;right;
    }
    return current;
}

Node* deleteNode(Node* root, int target, bool useSuccessor = true) {
    if (!root) {
        return root;
    }

    if (target &amp;#x3C; root-&gt;val) {
        root-&gt;left = deleteNode(root-&gt;left, target, useSuccessor);
    } else if (target &gt; root-&gt;val) {
        root-&gt;right = deleteNode(root-&gt;right, target, useSuccessor);
    } else if (!root-&gt;left &amp;#x26;&amp;#x26; !root-&gt;right) {
        delete root;
        return nullptr;
    } else if (!root-&gt;left) {
        auto tmp = root-&gt;right;
        delete root;
        return tmp;
    } else if (!root-&gt;right) {
        auto tmp = root-&gt;left;
        delete root;
        return tmp;
    } else {
        if (useSuccessor) {
            auto replacement = findSuccessor(root);
            root-&gt;val = replacement-&gt;val;
            root-&gt;right = deleteNode(root-&gt;right, replacement-&gt;val, useSuccessor);
        } else {
            auto replacement = findPredecessor(root);
            root-&gt;val = replacement-&gt;val;
            root-&gt;left = deleteNode(root-&gt;left, replacement-&gt;val, useSuccessor);
        }
    }
    return root;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Node* findSuccessor(Node* node) {
Node* current = node-&gt;right;
while (current-&gt;left) {
current = current-&gt;left;
}
return current;
}&lt;/p&gt;
&lt;p&gt;Node* findPredecessor(Node* node) {
Node* current = node-&gt;left;
while (current-&gt;right) {
current = current-&gt;right;
}
return current;
}&lt;/p&gt;
&lt;p&gt;Node* deleteNode(Node* root, int target, int useSuccessor) {
if (root == NULL) {
return root;
}
if (target &amp;#x3C; root-&gt;val) {
root-&gt;left = deleteNode(root-&gt;left, target, useSuccessor);
} else if (target &gt; root-&gt;val) {
root-&gt;right = deleteNode(root-&gt;right, target, useSuccessor);
} else if (!root-&gt;left &amp;#x26;&amp;#x26; !root-&gt;right) {
free(root);
return NULL;
} else if (!root-&gt;left) {
Node* tmp = root-&gt;right;
free(root);
return tmp;
} else if (!root-&gt;right) {
Node* tmp = root-&gt;left;
free(root);
return tmp;
} else {
if (useSuccessor) {
Node* replacement = findSuccessor(root);
root-&gt;val = replacement-&gt;val;
root-&gt;right = deleteNode(root-&gt;right, replacement-&gt;val, useSuccessor);
} else {
Node* replacement = findPredecessor(root);
root-&gt;val = replacement-&gt;val;
root-&gt;left = deleteNode(root-&gt;left, replacement-&gt;val, useSuccessor);
}
}
return root;
}&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;#x3C;/TabItem&gt;
&amp;#x3C;TabItem label=&quot;Rust&quot;&gt;
```rs
fn find_successor(node: &amp;#x26;Node) -&gt; i32 {
    let mut cur = node;
    while let Some(ref left) = cur.left {
        cur = left;
    }
    cur.val
}

fn find_predecessor(node: &amp;#x26;Node) -&gt; i32 {
    let mut cur = node;
    while let Some(ref right) = cur.right {
        cur = right;
    }
    cur.val
}

pub fn delete_node(root: Option&amp;#x3C;Box&amp;#x3C;Node&gt;&gt;, target: i32, use_successor: bool) -&gt; Option&amp;#x3C;Box&amp;#x3C;Node&gt;&gt; {
    match root {
        None =&gt; None,
        Some(mut node) =&gt; {
            if target &amp;#x3C; node.val {
                node.left = delete_node(node.left.take(), target, use_successor);
                Some(node)
            } else if target &gt; node.val {
                node.right = delete_node(node.right.take(), target, use_successor);
                Some(node)
            } else {
                match (node.left.take(), node.right.take()) {
                    (None, None) =&gt; None,
                    (left, None) =&gt; left,
                    (None, right) =&gt; right,
                    (left, right) =&gt; {
                        let repl = if use_successor {
                            find_successor(&amp;#x26;*right.as_ref().unwrap())
                        } else {
                            find_predecessor(&amp;#x26;*left.as_ref().unwrap())
                        };
                        node.val = repl;
                        if use_successor {
                            node.right = delete_node(right, repl, true);
                            node.left = left;
                        } else {
                            node.left = delete_node(left, repl, false);
                            node.right = right;
                        }
                        Some(node)
                    }
                }
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Iterative&lt;/h3&gt;
&lt;p&gt;Note that the helper functions now also return the current node and the parent node as a tuple.&lt;/p&gt;
&lt;p&gt;def find_predecessor(node):
current = node.left
parent = node
while current.right is not None:
parent = current
current = current.right
return current, parent&lt;/p&gt;
&lt;p&gt;def delete(root, key, use_successor=True):
if root is None:
return root&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;parent = None
current = root

while current is not None and current.val != key:
    parent = current
    current = current.left if key &amp;#x3C; current.val else current.right

if current is None:
    return root

if current.left is None and current.right is None:
    if parent is None:
        return None  
    elif parent.left == current:
        parent.left = None
    else:
        parent.right = None

elif current.left is None:
    if parent is None:
        return current.right
    elif parent.left == current:
        parent.left = current.right
    else:
        parent.right = current.right

elif current.right is None:
    if parent is None:
        return current.left 
    elif parent.left == current:
        parent.left = current.left
    else:
        parent.right = current.left

else:
    if use_successor:
        replacement, replacement_parent = find_successor(current)
    else:
        replacement, replacement_parent = find_predecessor(current)

    current.val = replacement.val

    if use_successor:
        if replacement_parent == current:
            replacement_parent.right = replacement.right
        else:
            replacement_parent.left = replacement.right
    elif replacement_parent == current:
        replacement_parent.left = replacement.left
    else:
        replacement_parent.right = replacement.left

return root
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;&amp;#x3C;/TabItem&gt;
&amp;#x3C;TabItem label=&quot;Javascript&quot;&gt;
```js
function findSuccessor(node) {
    let current = node.right;
    let parent = node;
    while (current.left !== null) {
        parent = current;
        current = current.left;
    }
    return [current, parent];
}

function findPredecessor(node) {
    let current = node.left;
    let parent = node;
    while (current.right !== null) {
        parent = current;
        current = current.right;
    }
    return [current, parent];
}

function deleteNode(root, key, useSuccessor = true) {
    if (root === null) {
      return root;
    }

    let parent  = null;
    let current = root;
    
    while (current !== null &amp;#x26;&amp;#x26; current.val !== key) {
        parent  = current;
        current = key &amp;#x3C; current.val ? current.left : current.right;
    }
    if (current === null) {
        return root;
    }

    switch (true) {
      case (current.left === null &amp;#x26;&amp;#x26; current.right === null):
        if (parent === null) {
          root = null;
        } else if (parent.left === current) {
          parent.left = null;
        } else {
          parent.right = null;
        }
        break;

      case (current.left === null):
        if (parent === null) {
          root = current.right;
        } else if (parent.left === current) {
          parent.left = current.right;
        } else {
          parent.right = current.right;
        }
        break;

      case (current.right === null):
        if (parent === null) {
          root = current.left;
        } else if (parent.left === current) {
          parent.left = current.left;
        } else {
          parent.right = current.left;
        }
        break;

      default: {
        const [replacement, replacementParent] = useSuccessor
          ? findSuccessor(current)
          : findPredecessor(current);

        current.val = replacement.val;

        if (useSuccessor) {
          if (replacementParent === current) {
            replacementParent.right = replacement.right;
          } else {
            replacementParent.left =  replacement.right;
          }
        } else {
          if (replacementParent === current) {
            replacementParent.left = replacement.left;
          } else {
            replacementParent.right = replacement.left;
          }
        }
      }
    }

    return root;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;func findPredecessor(node *Node) (*Node, *Node) {
current := node.Left
parent := node
for current.Right != nil {
parent = current
current = current.Right
}
return current, parent
}&lt;/p&gt;
&lt;p&gt;func deleteNode(root *Node, key int, useSuccessor bool) *Node {
if root == nil {
return nil
}&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var parent *Node
current := root
for current != nil &amp;#x26;&amp;#x26; current.Val != key {
	parent = current
	if key &amp;#x3C; current.Val {
		current = current.Left
	} else {
		current = current.Right
	}
}

if current == nil {
	return root
}

switch {
case current.Left == nil &amp;#x26;&amp;#x26; current.Right == nil:
	if parent == nil {
		return nil
	} else if parent.Left == current {
		parent.Left = nil
	} else {
		parent.Right = nil
	}

case current.Left == nil:
	if parent == nil {
		return current.Right
	} else if parent.Left == current {
		parent.Left = current.Right
	} else {
		parent.Right = current.Right
	}

case current.Right == nil:
	if parent == nil {
		return current.Left
	} else if parent.Left == current {
		parent.Left = current.Left
	} else {
		parent.Right = current.Left
	}

default:
	var repl, replParent *Node
	if useSuccessor {
		repl, replParent = findSuccessor(current)
	} else {
		repl, replParent = findPredecessor(current)
	}
	current.Val = repl.Val

	if useSuccessor {
		if replParent == current {
			replParent.Right = repl.Right
		} else {
			replParent.Left = repl.Right
		}
	} else {
		if replParent == current {
			replParent.Left = repl.Left
		} else {
			replParent.Right = repl.Left
		}
	}
}

return root
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;#x3C;/TabItem&gt;
&amp;#x3C;TabItem label=&quot;Java&quot;&gt;
```java
class NodeParentPair {
    Node node;
    Node parent;
    
    NodeParentPair(Node node, Node parent) {
        this.node = node;
        this.parent = parent;
    }
}

public class BSTDeletion {
    
    public static NodeParentPair findSuccessor(Node node) {
        Node current = node.right;
        Node parent = node;
        while (current.left != null) {
            parent = current;
            current = current.left;
        }
        return new NodeParentPair(current, parent);
    }
    
    public static NodeParentPair findPredecessor(Node node) {
        Node current = node.left;
        Node parent = node;
        while (current.right != null) {
            parent = current;
            current = current.right;
        }
        return new NodeParentPair(current, parent);
    }
    
    public static Node deleteNode(Node root, int key, boolean useSuccessor) {
        if (root == null) {
            return root;
        }
        
        Node parent = null;
        Node current = root;
        
        while (current != null &amp;#x26;&amp;#x26; current.val != key) {
            parent = current;
            current = key &amp;#x3C; current.val ? current.left : current.right;
        }
        
        if (current == null) {
            return root;
        }
        
        if (current.left == null &amp;#x26;&amp;#x26; current.right == null) {
            if (parent == null) {
                return null;
            } else if (parent.left == current) {
                parent.left = null;
            } else {
                parent.right = null;
            }
        }
        else if (current.left == null) {
            if (parent == null) {
                return current.right;
            } else if (parent.left == current) {
                parent.left = current.right;
            } else {
                parent.right = current.right;
            }
        }
        else if (current.right == null) {
            if (parent == null) {
                return current.left;
            } else if (parent.left == current) {
                parent.left = current.left;
            } else {
                parent.right = current.left;
            }
        }
        else {
            NodeParentPair replacementPair;
            if (useSuccessor) {
                replacementPair = findSuccessor(current);
            } else {
                replacementPair = findPredecessor(current);
            }
            
            Node replacement = replacementPair.node;
            Node replacementParent = replacementPair.parent;
            
            current.val = replacement.val;
            
            if (useSuccessor) {
                if (replacementParent == current) {
                    replacementParent.right = replacement.right;
                } else {
                    replacementParent.left = replacement.right;
                }
            } else {
                if (replacementParent == current) {
                    replacementParent.left = replacement.left;
                } else {
                    replacementParent.right = replacement.left;
                }
            }
        }
        
        return root;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;public NodeParentPair(Node node, Node parent) 
{
    Node = node;
    Parent = parent;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;/p&gt;
&lt;p&gt;public class BSTDeletion
{
public static NodeParentPair FindSuccessor(Node node)
{
Node current = node.right;
Node parent = node;
while (current.left != null)
{
parent = current;
current = current.left;
}
return new NodeParentPair(current, parent);
}&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public static NodeParentPair FindPredecessor(Node node) 
{
    Node current = node.left;
    Node parent = node;
    while (current.right != null) 
    {
        parent = current;
        current = current.right;
    }
    return new NodeParentPair(current, parent);
}

public static Node DeleteNode(Node root, int key, bool useSuccessor = true) 
{
    if (root == null) 
    {
        return root;
    }
    
    Node parent = null;
    Node current = root;
    
    while (current != null &amp;#x26;&amp;#x26; current.val != key) 
    {
        parent = current;
        current = key &amp;#x3C; current.val ? current.left : current.right;
    }
    
    if (current == null) 
    {
        return root;
    }
    
    if (current.left == null &amp;#x26;&amp;#x26; current.right == null) 
    {
        if (parent == null) 
        {
            return null;
        } 
        else if (parent.left == current) 
        {
            parent.left = null;
        } 
        else 
        {
            parent.right = null;
        }
    }
    else if (current.left == null) 
    {
        if (parent == null) 
        {
            return current.right;
        } 
        else if (parent.left == current) 
        {
            parent.left = current.right;
        } 
        else 
        {
            parent.right = current.right;
        }
    }
    else if (current.right == null) 
    {
        if (parent == null) 
        {
            return current.left;
        } 
        else if (parent.left == current) 
        {
            parent.left = current.left;
        } 
        else 
        {
            parent.right = current.left;
        }
    }
    else 
    {
        NodeParentPair replacementPair;
        if (useSuccessor) 
        {
            replacementPair = FindSuccessor(current);
        } 
        else 
        {
            replacementPair = FindPredecessor(current);
        }
        
        Node replacement = replacementPair.Node;
        Node replacementParent = replacementPair.Parent;
        
        current.val = replacement.val;
        
        if (useSuccessor) 
        {
            if (replacementParent == current) 
            {
                replacementParent.right = replacement.right;
            } 
            else 
            {
                replacementParent.left = replacement.right;
            }
        } 
        else 
        {
            if (replacementParent == current) 
            {
                replacementParent.left = replacement.left;
            } 
            else 
            {
                replacementParent.right = replacement.left;
            }
        }
    }
    
    return root;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;#x3C;/TabItem&gt;
&amp;#x3C;TabItem label=&quot;C++&quot;&gt;
```cpp
std::pair&amp;#x3C;Node*, Node*&gt; findSuccessor(Node* node) {
    Node* current = node-&gt;right;
    Node* parent = node;
    while (current-&gt;left != nullptr) {
        parent = current;
        current = current-&gt;left;
    }
    return std::make_pair(current, parent);
}

std::pair&amp;#x3C;Node*, Node*&gt; findPredecessor(Node* node) {
    Node* current = node-&gt;left;
    Node* parent = node;
    while (current-&gt;right != nullptr) {
        parent = current;
        current = current-&gt;right;
    }
    return std::make_pair(current, parent);
}

Node* deleteNode(Node* root, int key, bool useSuccessor = true) {
    if (root == nullptr) {
        return root;
    }
    
    Node* parent = nullptr;
    Node* current = root;
    
    while (current != nullptr &amp;#x26;&amp;#x26; current-&gt;val != key) {
        parent = current;
        current = key &amp;#x3C; current-&gt;val ? current-&gt;left : current-&gt;right;
    }
    
    if (current == nullptr) {
        return root;
    }
    
    if (current-&gt;left == nullptr &amp;#x26;&amp;#x26; current-&gt;right == nullptr) {
        if (parent == nullptr) {
            delete current;
            return nullptr;
        } else if (parent-&gt;left == current) {
            parent-&gt;left = nullptr;
        } else {
            parent-&gt;right = nullptr;
        }
        delete current;
    }
    else if (current-&gt;left == nullptr) {
        Node* rightChild = current-&gt;right;
        if (parent == nullptr) {
            delete current;
            return rightChild;
        } else if (parent-&gt;left == current) {
            parent-&gt;left = rightChild;
        } else {
            parent-&gt;right = rightChild;
        }
        delete current;
    }
    else if (current-&gt;right == nullptr) {
        Node* leftChild = current-&gt;left;
        if (parent == nullptr) {
            delete current;
            return leftChild;
        } else if (parent-&gt;left == current) {
            parent-&gt;left = leftChild;
        } else {
            parent-&gt;right = leftChild;
        }
        delete current;
    }
    else {
        std::pair&amp;#x3C;Node*, Node*&gt; replacementPair;
        if (useSuccessor) {
            replacementPair = findSuccessor(current);
        } else {
            replacementPair = findPredecessor(current);
        }
        
        Node* replacement = replacementPair.first;
        Node* replacementParent = replacementPair.second;
        
        current-&gt;val = replacement-&gt;val;
        
        if (useSuccessor) {
            if (replacementParent == current) {
                replacementParent-&gt;right = replacement-&gt;right;
            } else {
                replacementParent-&gt;left = replacement-&gt;right;
            }
        } else {
            if (replacementParent == current) {
                replacementParent-&gt;left = replacement-&gt;left;
            } else {
                replacementParent-&gt;right = replacement-&gt;left;
            }
        }
        delete replacement;
    }
    
    return root;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;NodeParentPair findPredecessor(Node* node) {
Node* current = node-&gt;left;
Node* parent = node;
while (current-&gt;right != NULL) {
parent = current;
current = current-&gt;right;
}
NodeParentPair pair = {current, parent};
return pair;
}&lt;/p&gt;
&lt;p&gt;Node* deleteNode(Node* root, int key, bool useSuccessor) {
if (root == NULL) {
return root;
}&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Node* parent = NULL;
Node* current = root;

while (current != NULL &amp;#x26;&amp;#x26; current-&gt;val != key) {
    parent = current;
    current = key &amp;#x3C; current-&gt;val ? current-&gt;left : current-&gt;right;
}

if (current == NULL) {
    return root;
}

if (current-&gt;left == NULL &amp;#x26;&amp;#x26; current-&gt;right == NULL) {
    if (parent == NULL) {
        free(current);
        return NULL;
    } else if (parent-&gt;left == current) {
        parent-&gt;left = NULL;
    } else {
        parent-&gt;right = NULL;
    }
    free(current);
}
else if (current-&gt;left == NULL) {
    Node* rightChild = current-&gt;right;
    if (parent == NULL) {
        free(current);
        return rightChild;
    } else if (parent-&gt;left == current) {
        parent-&gt;left = rightChild;
    } else {
        parent-&gt;right = rightChild;
    }
    free(current);
}
else if (current-&gt;right == NULL) {
    Node* leftChild = current-&gt;left;
    if (parent == NULL) {
        free(current);
        return leftChild;
    } else if (parent-&gt;left == current) {
        parent-&gt;left = leftChild;
    } else {
        parent-&gt;right = leftChild;
    }
    free(current);
}
else {
    NodeParentPair replacementPair;
    if (useSuccessor) {
        replacementPair = findSuccessor(current);
    } else {
        replacementPair = findPredecessor(current);
    }
    
    Node* replacement = replacementPair.node;
    Node* replacementParent = replacementPair.parent;
    
    current-&gt;val = replacement-&gt;val;
    
    if (useSuccessor) {
        if (replacementParent == current) {
            replacementParent-&gt;right = replacement-&gt;right;
        } else {
            replacementParent-&gt;left = replacement-&gt;right;
        }
    } else {
        if (replacementParent == current) {
            replacementParent-&gt;left = replacement-&gt;left;
        } else {
            replacementParent-&gt;right = replacement-&gt;left;
        }
    }
    free(replacement);
}

return root;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;/p&gt;
&lt;p&gt;void freeTree(Node* root) {
if (root != NULL) {
freeTree(root-&gt;left);
freeTree(root-&gt;right);
free(root);
}
}&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;#x3C;/TabItem&gt;
&amp;#x3C;TabItem label=&quot;Rust&quot;&gt;
```rs
fn remove_successor(node: &amp;#x26;mut Node) -&gt; i32 {
    if let Some(mut right) = node.right.take() {
        if right.left.is_none() {
            let val = right.val;
            node.right = right.right.take();
            val
        } else {
            let mut current = &amp;#x26;mut right;
            while current.left.as_ref().unwrap().left.is_some() {
                current = current.left.as_mut().unwrap();
            }
            let mut successor = current.left.take().unwrap();
            let val = successor.val;
            current.left = successor.right.take();
            node.right = Some(right);
            val
        }
    } else {
        node.val
    }
}

fn remove_predecessor(node: &amp;#x26;mut Node) -&gt; i32 {
    if let Some(mut left) = node.left.take() {
        if left.right.is_none() {
            let val = left.val;
            node.left = left.left.take();
            val
        } else {
            let mut current = &amp;#x26;mut left;
            while current.right.as_ref().unwrap().right.is_some() {
                current = current.right.as_mut().unwrap();
            }
            let mut predecessor = current.right.take().unwrap();
            let val = predecessor.val;
            current.right = predecessor.left.take();
            node.left = Some(left);
            val
        }
    } else {
        node.val
    }
}

pub fn delete_node(root: Option&amp;#x3C;Box&amp;#x3C;Node&gt;&gt;, key: i32, use_successor: bool) -&gt; Option&amp;#x3C;Box&amp;#x3C;Node&gt;&gt; {
    let mut root = root?;
    
    if root.val == key {
        return match (&amp;#x26;root.left, &amp;#x26;root.right) {
            (None, None) =&gt; None,
            (Some(_), None) =&gt; root.left.take(),
            (None, Some(_)) =&gt; root.right.take(),
            (Some(_), Some(_)) =&gt; {
                if use_successor {
                    root.val = remove_successor(&amp;#x26;mut root);
                } else {
                    root.val = remove_predecessor(&amp;#x26;mut root);
                }
                Some(root)
            }
        };
    }
    
    let mut current = &amp;#x26;mut root;
    loop {
        let go_left = key &amp;#x3C; current.val;
        let target = if go_left { &amp;#x26;mut current.left } else { &amp;#x26;mut current.right };
        
        match target {
            None =&gt; return Some(root),
            Some(node) if node.val == key =&gt; {
                match (&amp;#x26;node.left, &amp;#x26;node.right) {
                    (None, None) =&gt; {
                        *target = None;
                    }
                    (Some(_), None) =&gt; {
                        *target = node.left.take();
                    }
                    (None, Some(_)) =&gt; {
                        *target = node.right.take();
                    }
                    (Some(_), Some(_)) =&gt; {
                        if use_successor {
                            node.val = remove_successor(node);
                        } else {
                            node.val = remove_predecessor(node);
                        }
                    }
                }
                break;
            }
            Some(node) =&gt; {
                current = node;
            }
        }
    }
    
    Some(root)
}

&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;Time Complexity&lt;/h2&gt;
&lt;p&gt;For any BST operation (search, insert, delete), the time complexity is O(h) where h is the tree&apos;s height.&lt;/p&gt;
&lt;p&gt;| Tree Structure | Height (h) | Time Complexity | Example |
|:---------------|:-----------|:----------------|:--------|
| &lt;strong&gt;Balanced&lt;/strong&gt; | O(log n) | &lt;strong&gt;O(log n)&lt;/strong&gt; | Perfect binary tree |
| &lt;strong&gt;Unbalanced&lt;/strong&gt; | O(n) | &lt;strong&gt;O(n)&lt;/strong&gt; | Linked list structure / skewed |&lt;/p&gt;
&lt;h3&gt;Balanced Trees (Best Case)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Height approximately log₂ n&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Performance&lt;/strong&gt;: O(log n) for all operations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Example&lt;/strong&gt;: With 1,000 nodes, height ≈ 10 levels&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Skewed Trees (Worst Case)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Height equals n (essentially a linked list)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Performance&lt;/strong&gt;: O(n) for all operations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Example&lt;/strong&gt;: Inserting sorted data: 1→2→3→4→5&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;Space Complexity&lt;/h2&gt;
&lt;p&gt;| Implementation | Space Complexity | Notes |
|:---------------|:----------------|:------|
| &lt;strong&gt;Recursive&lt;/strong&gt; | O(h) | Call stack grows with height |
| &lt;strong&gt;Iterative&lt;/strong&gt; | O(1) | Constant extra space |&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Why Balancing Matters&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Without balancing:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sorted input creates O(n) height&lt;/li&gt;
&lt;li&gt;Performance degrades to linear search&lt;/li&gt;
&lt;li&gt;Defeats the purpose of using a BST&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;With self-balancing (AVL, Red-Black):&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Automatic rotations maintain O(log n) height&lt;/li&gt;
&lt;li&gt;Guaranteed logarithmic performance&lt;/li&gt;
&lt;li&gt;Slight overhead for rebalancing operations&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;Common Pitfalls&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Inserting sorted data leads to an unbalanced, degenerate tree (poor performance).&lt;/li&gt;
&lt;li&gt;BST property violations can occur with incorrect insert/delete logic.&lt;/li&gt;
&lt;li&gt;Duplicate values: handle as per requirements (left/right, or disallow).&lt;/li&gt;
&lt;li&gt;Memory leaks in C/C++ when forgetting to free deleted nodes.&lt;/li&gt;
&lt;li&gt;Parent pointer management errors during deletion (especially with two children).&lt;/li&gt;
&lt;li&gt;Edge case bugs: deleting root node, single-node trees, or non-existent keys.&lt;/li&gt;
&lt;li&gt;Mixing recursive/iterative approaches inconsistently in the same codebase.&lt;/li&gt;
&lt;li&gt;Assuming balanced performance without using self-balancing variants.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Best Practice:&lt;/strong&gt; Use self-balancing BSTs (AVL, Red-Black) for guaranteed O(log n) performance.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Height Determines Performance:&lt;/strong&gt; All BST operations are O(h); balanced trees ensure optimal performance.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Iterative vs. Recursive:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Recursive&lt;/strong&gt; approaches are simpler and easier to understand, suitable when height (h) is guaranteed to be O(log n).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Iterative&lt;/strong&gt; approaches can optimize space complexity O(1), beneficial when height could be large (potentially O(n)) or in memory-constrained environments.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;What&apos;s Next&lt;/h2&gt;
&lt;p&gt;In &lt;strong&gt;Post 4: Self-Balancing Trees - AVL and Red-Black&lt;/strong&gt;, we&apos;ll build on our BST foundations to see:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Rotations&lt;/strong&gt; (left &amp;#x26; right) step by step&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Invariants&lt;/strong&gt; that distinguish AVL vs. Red-Black trees&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;When to pick which&lt;/strong&gt; structure for your use case&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>Binary Tree Traversal: Navigating Binary Trees Effectively</title><link>https://chillymosh.com/blog/binary-tree-traversal</link><guid isPermaLink="true">https://chillymosh.com/blog/binary-tree-traversal</guid><description>Learn about recursive and iterative traversals—including pre-order, in-order, post-order, and level-order—with practical examples and real-world use-cases.</description><pubDate>Mon, 14 Jul 2025 19:45:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside, Collapse, MdxRepl, Tabs, TabItem } from &quot;astro-pure/user&quot;
import TreeExample from &quot;tree-traversal.svg&quot;;
import NodeCycler from &apos;@/components/NodeCycler.astro&apos;;&lt;/p&gt;
&lt;p&gt;In our previous post, &lt;a href=&quot;../binary-trees-101&quot;&gt;Binary Trees 101&lt;/a&gt;,  we introduced the basics of binary trees, covering their structures, properties, and practical representations. Now that you&apos;ve got a solid grasp of binary tree fundamentals, let&apos;s explore one of the most essential concepts when working with these data structures: tree traversals.&lt;/p&gt;
&lt;p&gt;Traversals allow us to systematically visit each node in a binary tree, serving as the foundation for many critical operations like searching, inserting, and deleting data.&lt;/p&gt;
&lt;h2&gt;Key Topics&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Recursive vs Iterative Approaches:&lt;/strong&gt; The two main strategies for traversing trees.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Traversal Techniques:&lt;/strong&gt; Pre-order, in-order, post-order, and level-order traversals, each with distinct characteristics and use-cases.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Real-world Applications:&lt;/strong&gt; Understanding when and why to use each traversal type, such as in-order for Binary Search Trees (BSTs).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;DFS vs BFS Traversals&lt;/h2&gt;
&lt;p&gt;There are two fundamental strategies for traversing trees:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Depth-First Search (DFS):&lt;/strong&gt;&lt;br&gt;
Explores as deep as possible down each branch before backtracking.&lt;br&gt;
&lt;em&gt;Includes:&lt;/em&gt; Pre-order, In-order, and Post-order traversals.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Breadth-First Search (BFS):&lt;/strong&gt;&lt;br&gt;
Explores all nodes at each level before moving to the next.&lt;br&gt;
&lt;em&gt;Includes:&lt;/em&gt; Level-order traversal.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Types of Tree Traversals&lt;/h2&gt;
&lt;p&gt;Before diving into code, let&apos;s cover the core strategies for traversing a binary tree:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Recursive:&lt;/strong&gt; Leverages the language&apos;s call stack, naturally mirroring the tree&apos;s structure. Most traversal algorithms are elegant and concise in recursive form.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Iterative:&lt;/strong&gt; Uses explicit data structures like stacks (for DFS) or queues (for BFS) to manage traversal state, which can avoid recursion limits and sometimes improve performance.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Traversal Orders &amp;#x26; Use-Cases&lt;/h3&gt;
&lt;p&gt;| Traversal      | Order                   | Real-World Uses                                 | DFS/BFS |
|:-------------- |:----------------------- |:----------------------------------------------- |:-------:|
| &lt;strong&gt;Pre-order&lt;/strong&gt;  | Root, Left, Right       | Cloning trees, prefix expression trees          |  DFS    |
| &lt;strong&gt;In-order&lt;/strong&gt;   | Left, Root, Right       | Retrieving sorted data from BSTs                |  DFS    |
| &lt;strong&gt;Post-order&lt;/strong&gt; | Left, Right, Root       | Deleting/freeing trees, postfix evaluation      |  DFS    |
| &lt;strong&gt;Level-order&lt;/strong&gt;| Level by level (L→R)    | Printing, serialisation, BFS/shortest path      |  BFS    |&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Traversal Examples&lt;/h2&gt;
&lt;h3&gt;Pre-order Traversal (Root, Left, Right)&lt;/h3&gt;
&lt;p&gt;Pre-order traversal visits the root node first, then explores the left subtree, followed by the right subtree.
It&apos;s called pre-order because each node is processed before its left and right subtrees are visited.&lt;/p&gt;
&lt;p&gt;&amp;#x3C;NodeCycler
SvgComponent={TreeExample}
nodeOrder={[&apos;R&apos;,&apos;A&apos;,&apos;C&apos;,&apos;D&apos;,&apos;B&apos;,&apos;E&apos;,&apos;F&apos;,&apos;G&apos;]}
interval={600}
/&gt;&lt;/p&gt;
&lt;h4&gt;Recursive&lt;/h4&gt;
&lt;p&gt;The recursive version uses the programming language&apos;s call stack to keep track of where it is in the tree.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Visit the Node:&lt;/strong&gt; At each function call, process the current node (e.g. print its value or add it to a result list).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Traverse Left:&lt;/strong&gt; Recursively call the function on the node&apos;s left child. This continues down the leftmost path first.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Traverse Right:&lt;/strong&gt; After finishing the left subtree, recursively call the function on the node&apos;s right child.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Base Case:&lt;/strong&gt; If the function reaches a node that is None (or null), it returns immediately—meaning there&apos;s nothing left to visit at that branch.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;Iterative&lt;/h4&gt;
&lt;p&gt;Instead of using recursion, the iterative version explicitly manages the nodes to visit using a stack.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Initialization:&lt;/strong&gt; Start by pushing the root node onto the stack.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Processing:&lt;/strong&gt; While the stack isn&apos;t empty, pop the top node, visit it (add its value to the result list), and then push its right and left children (if any) onto the stack.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Order:&lt;/strong&gt; By pushing the right child before the left child, we ensure the left child is processed first (since stacks are LIFO—last in, first out).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Repeat:&lt;/strong&gt; Continue this process until there are no more nodes left in the stack.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;public class TreeTraversal {
    public List&amp;#x3C;Integer&gt; preorder(Node root) {
        List&amp;#x3C;Integer&gt; result = new ArrayList&amp;#x3C;&gt;();
        if (root == null) return result;
        Deque&amp;#x3C;Node&gt; stack = new ArrayDeque&amp;#x3C;&gt;();
        stack.push(root);
        while (!stack.isEmpty()) {
            Node node = stack.pop();
            result.add(node.val);
            if (node.right != null) stack.push(node.right);
            if (node.left  != null) stack.push(node.left);
        }
        return result;
    }
}
```
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;In-order Traversal (Left, Root, Right)&lt;/h3&gt;
&lt;p&gt;In-order traversal explores the left subtree first, then processes the root node, and finally visits the right subtree.
This approach is especially valuable for Binary Search Trees, as it retrieves values in ascending order.&lt;/p&gt;
&lt;p&gt;It&apos;s called in-order because each node is visited between traversing its left and right subtrees, after finishing the left, but before starting the right.&lt;/p&gt;
&lt;p&gt;&amp;#x3C;NodeCycler
SvgComponent={TreeExample}
nodeOrder={[&quot;C&quot;, &quot;A&quot;, &quot;D&quot;, &quot;R&quot;, &quot;E&quot;, &quot;B&quot;, &quot;G&quot;, &quot;F&quot;]}
interval={600}
/&gt;&lt;/p&gt;
&lt;h4&gt;Recursive&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Traverse Left:&lt;/strong&gt; Recursively call the function on the node&apos;s left child, moving as far left as possible in the tree.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Visit the Node:&lt;/strong&gt; Once there are no more left children, process the current node (e.g. print its value or add it to a result list).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Traverse Right:&lt;/strong&gt; After visiting the node, recursively call the function on the right child to process the right subtree.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Base Case:&lt;/strong&gt; If the function reaches a None (or null) node, it returns immediately—meaning there&apos;s nothing left to visit in that branch.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;Iterative&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Initialization:&lt;/strong&gt; Start with an empty stack and set the current node to the root.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Go Left:&lt;/strong&gt; Move as far left as possible, pushing each node onto the stack as you go.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Visit the Node:&lt;/strong&gt; Once you reach a node with no left child, pop a node from the stack and process it (print/add its value).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Go Right:&lt;/strong&gt; After visiting the node, move to its right child and repeat the process.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Repeat:&lt;/strong&gt; Continue this loop (go left, visit, go right) until both the stack is empty and there are no more nodes to process.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3&gt;Post-order Traversal (Left, Right, Root)&lt;/h3&gt;
&lt;p&gt;Post-order traversal explores the left subtree first, then the right subtree, and processes the current node last.
This approach is particularly useful for tasks like deleting a tree, since you want to delete children before the parent, or evaluating postfix expressions.&lt;/p&gt;
&lt;p&gt;It&apos;s called post-order because each node is processed after both its subtrees have been visited.&lt;/p&gt;
&lt;p&gt;&amp;#x3C;NodeCycler
SvgComponent={TreeExample}
nodeOrder={[&quot;C&quot;, &quot;D&quot;, &quot;A&quot;, &quot;E&quot;, &quot;G&quot;, &quot;F&quot;, &quot;B&quot;, &quot;R&quot;]}
interval={600}
/&gt;&lt;/p&gt;
&lt;h4&gt;Recursive&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Traverse Left:&lt;/strong&gt; Recursively call the function on the node&apos;s left child.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Traverse Right:&lt;/strong&gt; Recursively call the function on the node&apos;s right child.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Visit the Node:&lt;/strong&gt; Process the current node (e.g. print its value or add it to a result list).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Base Case:&lt;/strong&gt; If the function reaches a node that is None (or null), it returns immediately.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;Iterative&lt;/h4&gt;
&lt;p&gt;Instead of recursion, the iterative version explicitly manages nodes to visit using a stack. Post-order is trickier to implement iteratively than pre-order or in-order.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Initialization:&lt;/strong&gt; Start by pushing the root node onto the stack.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Processing:&lt;/strong&gt; Pop nodes from the stack, and push them onto a &lt;em&gt;visited&lt;/em&gt; stack, or prepend their value to the result list.
Push left child first, then right child, so right is processed before left.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Repeat:&lt;/strong&gt; Continue until the stack is empty.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; The visited stack or reversed result list gives the post-order traversal.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This approach is ideal for collecting values in post-order, but not suitable for tree modification, like deleting nodes or freeing memory, where you must visit a node only after both children have been processed.&lt;/p&gt;
&lt;p&gt;While the &lt;em&gt;reverse pre-order&lt;/em&gt; trick is great for collecting values, sometimes you need to process a node only after both children are finished (e.g. to free nodes or modify the tree).
Here are two classic techniques for this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;One Stack With Visited State:&lt;/strong&gt;
Use a single stack but track the last &lt;em&gt;visited node&lt;/em&gt;. Only process a node after both its left and right children are done.
This approach closely matches recursion, but is more complex to implement.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Two Stack Method:&lt;/strong&gt;
Use one stack for traversal and a second stack, or list, to collect the nodes in post-order.
This also results in O(n) time and space.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These approaches are more advanced, and not usually needed for simple traversals, but they&apos;re useful to know about.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;Level-order Traversal (BFS)&lt;/h3&gt;
&lt;p&gt;Level-order traversal visits all nodes at each depth of the tree, starting from the root and working level by level from left to right.
This method is also known as breadth-first traversal, as it explores all nodes on one level before moving to the next.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Level-order traversal is essential for:&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Printing a tree level by level, like a hierarchy or org chart.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Finding the shortest path in an unweighted tree&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Serialising and deserialising trees for storage or transmission&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Solving problems that require processing nodes in order of their depth&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;#x3C;NodeCycler
SvgComponent={TreeExample}
nodeOrder={[&quot;R&quot;, &quot;A&quot;, &quot;B&quot;, &quot;C&quot;, &quot;D&quot;, &quot;E&quot;, &quot;F&quot;, &quot;G&quot;]}
interval={600}
/&gt;&lt;/p&gt;
&lt;p&gt;For the tree above, level-order traversal visits nodes by row, starting from the root: R, A, B, C, D, E, F, G.
This order ensures all nodes at depth n are visited before any node at depth n+1.&lt;/p&gt;
&lt;p&gt;Level-order traversal is most naturally implemented using a queue (FIFO), rather than a stack, or recursively.&lt;/p&gt;
&lt;h4&gt;Iterative&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Initialization:&lt;/strong&gt; Start by enqueueing the root node.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Processing:&lt;/strong&gt; While the queue isn&apos;t empty:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Dequeue the front node and process it (e.g. print/add its value).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enqueue its left child, if any, then its right child, if any.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;&lt;strong&gt;Repeat:&lt;/strong&gt; Continue until the queue is empty.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;def levelorder(root: Node):
    if not root:
        return []
    queue = deque([root])
    result = []
    while queue:
        node = queue.popleft()
        result.append(node.value)
        if node.left:
            queue.append(node.left)
        if node.right:
            queue.append(node.right)
    return result
```
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Recursive&lt;/h4&gt;
&lt;p&gt;Level-order traversal is rarely implemented recursively because recursion alone doesn&apos;t naturally handle breadth-first order.
However, it can be done using a helper function that visits nodes by their level:&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Recap&lt;/h2&gt;
&lt;p&gt;Hopefully, you now know the four essential ways to traverse a binary tree: pre-order, in-order, post-order, and level-order. Each with their own strengths, typical applications, and recursive or iterative solutions.
These patterns form the backbone of almost every binary tree algorithm, from basic searches to complex transformations.
The examples will be updated over time other languages.&lt;/p&gt;
&lt;h2&gt;There are even more advanced approaches, like Morris Traversal, that allow in-order traversal with O(1) extra space.
We&apos;ll cover these in a future post.
&amp;#x3C;/ Aside&gt;&lt;/h2&gt;
&lt;h2&gt;Up Next: Binary Search Trees (BSTs)&lt;/h2&gt;
&lt;p&gt;In our next post, we&apos;ll explore one of the most powerful uses of binary trees: the &lt;strong&gt;Binary Search Tree (BST)&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;We&apos;ll cover:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How BSTs organize data for fast search, insert, and delete operations&lt;/li&gt;
&lt;li&gt;Step-by-step BST algorithms (with code and visuals)&lt;/li&gt;
&lt;li&gt;How to analyse best-case and worst-case performance&lt;/li&gt;
&lt;li&gt;Common pitfalls, balancing needs, and when to use BSTs in practice&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Stay tuned for &lt;strong&gt;Binary Search Trees: Fast Search, Insert, &amp;#x26; Delete&lt;/strong&gt; coming next!&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>Binary Trees 101</title><link>https://chillymosh.com/blog/binary-trees-101</link><guid isPermaLink="true">https://chillymosh.com/blog/binary-trees-101</guid><description>An introduction to binary trees.</description><pubDate>Sat, 12 Jul 2025 21:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside, MdxRepl, Tabs, TabItem } from &quot;astro-pure/user&quot;
import BinaryTree from &quot;binary-tree.svg&quot;;
import BalancedTree from &quot;balanced.svg&quot;;
import FullTree from &quot;fulltree.svg&quot;;
import PerfectTree from &quot;perfect.svg&quot;;
import UnBalancedTree from &quot;unbalanced.svg&quot;;
import SkewedTree from &quot;skewed.svg&quot;;
import DegenTree from &quot;degenerate.svg&quot;;
import FileSys from &quot;filesys.svg&quot;;&lt;/p&gt;
&lt;p&gt;I was watching a friend stream the other day, and they were prepping for an upcoming interview. The topic being studied? Binary trees.&lt;/p&gt;
&lt;p&gt;This got me thinking about the role binary trees play in today&quot;s programming landscape. They&quot;re no longer just academic exercises; they&quot;ve become something of a rite of passage, popularized by coding challenge platforms like LeetCode and Codewars.&lt;/p&gt;
&lt;p&gt;In reality, most developers won&quot;t build a red-black tree every day on the job. Still, understanding their structures, properties, and traversal methods pays dividends across parsing, priority queues, file-system hierarchies, and beyond.&lt;/p&gt;
&lt;h2&gt;What is a Binary Tree?&lt;/h2&gt;
&lt;p&gt;A binary tree is a non-linear and hierarchical data structure in which each node has at most two children, commonly called left and right.
Unlike linear data structures (arrays/lists, linked lists, stacks, queues), binary trees organise data in a tree-like structure that allows for efficient searching, insertion, and deletion operations.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Node:&lt;/strong&gt; Holds a value (data)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Root:&lt;/strong&gt; The topmost node (entry point)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Edge:&lt;/strong&gt; The link between any two nodes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Child:&lt;/strong&gt; A node reachable directly from another node&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Leaf:&lt;/strong&gt; A node with no children&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Subtree:&lt;/strong&gt; Any node plus all its descendants&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Height of a Node:&lt;/strong&gt; Number of edges from the node to the deepest leaf&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Depth of a Node:&lt;/strong&gt; Number of edges from the root to the node.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Height of a Tree:&lt;/strong&gt; Height of the root node or the depth of the deepest node.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Representation&lt;/h2&gt;
&lt;p&gt;Before we take a look at the many types and algorithms for binary trees, it&quot;s important to understand how they are &lt;strong&gt;represented in code&lt;/strong&gt;.
The representation you choose can have a major impact on memory usage, performance, and which algorithms are practical.&lt;/p&gt;
&lt;p&gt;There are two common ways to represent a binary tree in practice:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Pointer (Node) Representation:&lt;/strong&gt;&lt;br&gt;
Each node is an object or struct, and has explicit references (&lt;strong&gt;pointers&lt;/strong&gt;) to its left and right child nodes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Array (Implicit) Representation:&lt;/strong&gt;&lt;br&gt;
The tree is mapped onto an array or list, with parent/child relationships determined by index calculations. This approach is especially efficient for &quot;complete&quot; binary trees, such as heaps.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let&quot;s see how these look.&lt;/p&gt;
&lt;h3&gt;Pointer&lt;/h3&gt;
&lt;p&gt;Each node contains a value, and pointers or references to its left and right children.&lt;br&gt;
This is the most general and flexible way to represent binary trees of &lt;em&gt;any&lt;/em&gt; shape. It is also easy to insert, remove, or restructure the tree.&lt;/p&gt;
&lt;p&gt;The trade off is that extra memory is used for pointers and may be slower for large trees.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public Node(int val=0, Node left=null, Node right=null) {
    this.val = val;
    this.left = left;
    this.right = right;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;&amp;#x3C;/TabItem&gt;
&amp;#x3C;TabItem label=&quot;C++&quot;&gt;
```cpp
//Struct
struct Node {
    int data;
    Node* left, * right;

    Node(int key) {
        data = key;
        left = nullptr;
        right = nullptr;
    }
};

//Class
class Node {
public:
    int data;
    Node* left, * right;

    Node(int key) {
        data = key;
        left = nullptr;
        right = nullptr;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;// Function to create a new tree node
struct Node* newNode(int item) {
struct Node* temp = (struct Node*)malloc(sizeof(struct Node));
temp-&gt;data = item;&lt;br&gt;
temp-&gt;left = temp-&gt;right = NULL;
return temp;
}&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;#x3C;/TabItem&gt;
&amp;#x3C;TabItem label=&quot;Rust&quot;&gt;
```rs
struct Node {
    val: i32,
    left: Option&amp;#x3C;Box&amp;#x3C;Node&gt;&gt;,
    right: Option&amp;#x3C;Box&amp;#x3C;Node&gt;&gt;,
}

impl Node {
    fn new(val: i32) -&gt; Self {
        Node {
            val,
            left: None,
            right: None,
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Array&lt;/h3&gt;
&lt;p&gt;A binary tree can also be stored as an array, where for any node at index i:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The left child is at 2i + 1&lt;/li&gt;
&lt;li&gt;The right child is at 2i + 2&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is especially efficient for complete binary trees (e.g. heaps).
Array representation does not require pointers, so memory is saved, and are cache friendly.&lt;/p&gt;
&lt;p&gt;They are not so good for sparse, skewed, or highly unbalanced trees. They are also hard to restructure dynamically.&lt;/p&gt;
&lt;p&gt;def left_child_index(index: int) -&gt; int:
return 2 * index + 1&lt;/p&gt;
&lt;p&gt;def right_child_index(index: int) -&gt; int:
return 2 * index + 2&lt;/p&gt;
&lt;p&gt;def get_data(index: int) -&gt; str | None:
if 0 &amp;#x3C;= index &amp;#x3C; len(binary_tree_array):
return binary_tree_array[index]
return None&lt;/p&gt;
&lt;p&gt;right_child = right_child_index(0)
left_child_of_right_child = left_child_index(right_child)
data = get_data(left_child_of_right_child)&lt;/p&gt;
&lt;p&gt;print(&quot;root.right.left.data:&quot;, data)&lt;/p&gt;
&lt;blockquote&gt;
&lt;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;F&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/blockquote&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;&amp;#x3C;/TabItem&gt;
&amp;#x3C;TabItem label=&quot;Javascript&quot;&gt;
```js
const binary_tree_array = [&quot;A&quot;, &quot;B&quot;, &quot;C&quot;, &quot;D&quot;, null, &quot;F&quot;];

function leftChildIndex(index) {
  return 2 * index + 1;
}

function rightChildIndex(index) {
  return 2 * index + 2;
}

function getData(index) {
  if (index &gt;= 0 &amp;#x26;&amp;#x26; index &amp;#x3C; binary_tree_array.length) {
    return binary_tree_array[index];
  }
  return null;
}

const right_child = rightChildIndex(0);
const left_child_of_right_child = leftChildIndex(right_child);
const data = getData(left_child_of_right_child);

console.log(&quot;root.right.left.data:&quot;, data);
&gt;&gt;&gt; F
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;import (
&quot;fmt&quot;
)&lt;/p&gt;
&lt;p&gt;func leftChildIndex(index int) int {
return 2*index + 1
}&lt;/p&gt;
&lt;p&gt;func rightChildIndex(index int) int {
return 2*index + 2
}&lt;/p&gt;
&lt;p&gt;func getData(tree []string, index int) string {
if index &gt;= 0 &amp;#x26;&amp;#x26; index &amp;#x3C; len(tree) {
return tree[index]
}
return &quot;nil&quot;
}&lt;/p&gt;
&lt;p&gt;func main() {
binaryTreeArray := []string{&quot;A&quot;, &quot;B&quot;, &quot;C&quot;, &quot;D&quot;, &quot;&quot;, &quot;F&quot;}&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rightChild := rightChildIndex(0)
leftChildOfRightChild := leftChildIndex(rightChild)
data := getData(binaryTreeArray, leftChildOfRightChild)

fmt.Println(&quot;root.right.left.data:&quot;, data)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;/p&gt;
&lt;blockquote&gt;
&lt;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;F&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/blockquote&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;&amp;#x3C;/TabItem&gt;
&amp;#x3C;TabItem label=&quot;Java&quot;&gt;
```java
public class BinaryTreeArrayExample {
    public static void main(String[] args) {
        String[] binaryTreeArray = {&quot;A&quot;, &quot;B&quot;, &quot;C&quot;, &quot;D&quot;, null, &quot;F&quot;};

        int rightChild = rightChildIndex(0);
        int leftChildOfRightChild = leftChildIndex(rightChild);
        String data = getData(binaryTreeArray, leftChildOfRightChild);

        System.out.println(&quot;root.right.left.data: &quot; + data);
    }

    static int leftChildIndex(int index) {
        return 2 * index + 1;
    }

    static int rightChildIndex(int index) {
        return 2 * index + 2;
    }

    static String getData(String[] tree, int index) {
        if (index &gt;= 0 &amp;#x26;&amp;#x26; index &amp;#x3C; tree.length) {
            return tree[index];
        }
        return null;
    }
}

&gt;&gt;&gt; F
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Which representation to use?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Use a &lt;strong&gt;pointer&lt;/strong&gt; for general purpose trees or when you know the tree can be irregular.&lt;/li&gt;
&lt;li&gt;Use an &lt;strong&gt;array&lt;/strong&gt; when you know the tree will always be complete or balanced.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;Types of Binary Tree&lt;/h2&gt;
&lt;h3&gt;By Shape&lt;/h3&gt;
&lt;p&gt;The &lt;em&gt;shape&lt;/em&gt; of a binary tree has a major impact on performance and on which algorithms apply:&lt;/p&gt;
&lt;p&gt;| Shape        | Definition                                                                                   | Example Use                    |
|--------------|----------------------------------------------------------------------------------------------|-------------------------------|
| &lt;strong&gt;Full&lt;/strong&gt;     | Every node has 0 or 2 children (no nodes with just one child).                               | Some balanced tree invariants  |
| &lt;strong&gt;Complete&lt;/strong&gt; | All levels are completely filled except possibly the last, which is filled left to right.     | Heaps (priority queues)        |
| &lt;strong&gt;Perfect&lt;/strong&gt;  | All internal nodes have 2 children; all leaves are at the same depth/level.                  | Theoretical analyses           |
| &lt;strong&gt;Skewed&lt;/strong&gt;   | Every node has only one child; all left (&lt;em&gt;left-skewed&lt;/em&gt;) or all right (&lt;em&gt;right-skewed&lt;/em&gt;).       | Worst-case BST degeneracy      |
| &lt;strong&gt;Degenerate&lt;/strong&gt;   | Every node has only one child; can be left or right.       | Linked lists; unbalanced BSTs       |
| &lt;strong&gt;Balanced&lt;/strong&gt; | Heights of left/right subtrees at every node differ by at most 1 (or a constant).            | AVL, Red-Black, fast search    |&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: Trees can satisfy multiple properties (e.g. a perfect tree is also full, complete, and balanced).&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;By Property or Use&lt;/h3&gt;
&lt;p&gt;Binary trees are also classified by the properties of their nodes or how they are used in algorithms:&lt;/p&gt;
&lt;p&gt;| Property/Type      | Definition                                                                            | Example Use                |
|--------------------|---------------------------------------------------------------------------------------|----------------------------|
| &lt;strong&gt;Binary Search Tree (BST)&lt;/strong&gt; | For each node, all values in the left subtree are less than the node, and all values in the right subtree are greater. Fast search, insert, delete. | Dictionary/map, database   |
| &lt;strong&gt;AVL Tree&lt;/strong&gt;       | Self-balancing BST (height difference at most 1 at each node).                        | Faster, guaranteed search  |
| &lt;strong&gt;Red-Black Tree&lt;/strong&gt; | Self-balancing BST with red/black color rules.                                        | Sets/maps in C++, Java     |
| &lt;strong&gt;Threaded Tree&lt;/strong&gt;  | Unused pointers point to in-order predecessor/successor for fast traversal.           | Fast in-order traversal    |
| &lt;strong&gt;Heap&lt;/strong&gt;           | Complete tree with heap property (min or max at the root).                            | Priority queues, heapsort  |
| &lt;strong&gt;Expression Tree&lt;/strong&gt;| Internal nodes are operators, leaves are operands.                                    | Arithmetic evaluation      |
| &lt;strong&gt;Huffman Tree&lt;/strong&gt;   | Binary tree for optimal prefix code (compression).                                    | Compression algorithms     |&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Basic Properties&lt;/h2&gt;
&lt;p&gt;When working with binary trees, there are two fundamental properties you&apos;ll regularly compute:&lt;/p&gt;
&lt;h3&gt;Size&lt;/h3&gt;
&lt;p&gt;The size of a binary tree is simply the total number of nodes it contains.
A straightforward way to compute this is using recursion, counting the current node plus all nodes in its left and right subtrees:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-py&quot;&gt;def size(node):
    if not node:
        return 0
    return 1 + size(node.left) + size(node.right)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Height&lt;/h3&gt;
&lt;p&gt;The height of a binary tree is defined as the length of the longest path from the root down to a leaf.
The height can be measured either by counting nodes or edges. Most commonly, nodes are counted.
Here&apos;s a simple recursive implementation:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-py&quot;&gt;def height(node):
    if not node:
        return 0
    return 1 + max(height(node.left), height(node.right))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These basic properties serve as the foundation for more advanced tree operations and analyses you&apos;ll encounter.&lt;/p&gt;
&lt;h3&gt;Other Useful Properties&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Depth (of a node):&lt;/strong&gt; Number of edges from the root node to a given node (root has depth 0).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Level:&lt;/strong&gt; Nodes at the same depth belong to the same level (root at level 0).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Width:&lt;/strong&gt; Maximum number of nodes at any single level of the tree.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Diameter:&lt;/strong&gt; Length of the longest path between any two nodes (doesn&apos;t necessarily pass through the root).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Root:&lt;/strong&gt; A (level 0, depth 0)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Children:&lt;/strong&gt; B, C (level 1)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Leaves:&lt;/strong&gt; D, E, F (level 2)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Width:&lt;/strong&gt; 3 nodes at the lowest level (D, E, F)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Diameter:&lt;/strong&gt; Longest path is D → B → A → C → F (4 edges)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Practical Analogy: File-System Structure&lt;/h2&gt;
&lt;p&gt;One of the best practical analogies is your computer&apos;s file-system hierarchy.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Directories are nodes that can contain both files and other folders.&lt;/li&gt;
&lt;li&gt;Files are leaves; they cannot contain anything else.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While actual directories can contain many children, we can still model this structure using a binary tree. The classic way to do this is called the left-child, right-sibling representation.&lt;/p&gt;
&lt;h3&gt;Left-Child, Right-Sibling Representation&lt;/h3&gt;
&lt;p&gt;When a directory contains more than two entries (files or subdirectories), we use a trick:&lt;/p&gt;
&lt;p&gt;The left child points to the first item (file or directory) inside the directory.&lt;/p&gt;
&lt;p&gt;The right child points to the next sibling at the same directory level.&lt;/p&gt;
&lt;p&gt;So, you&apos;d traverse left from the parent for the first child, then follow right children to reach the rest.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;What&apos;s Next?&lt;/h2&gt;
&lt;p&gt;Now that you&apos;ve got a solid grasp on binary tree shapes, properties, and representations, you&apos;re ready for the next step: &lt;strong&gt;tree traversals&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;In the upcoming post, we&apos;ll explore:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Recursive vs iterative approaches&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pre-order, in-order, post-order, and level-order traversals&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;When, and why, each traversal shines&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you&apos;re comfortable with nodes and tree structures, you&apos;ll breeze through traversal patterns and see how these foundational concepts play out in real algorithms.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Stay tuned for Part 2: Tree Traversals!&lt;/strong&gt;&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>Generics in Python: Simplifying Complexity</title><link>https://chillymosh.com/blog/generics-in-python-simplifying-complexity</link><guid isPermaLink="true">https://chillymosh.com/blog/generics-in-python-simplifying-complexity</guid><description>A walk through of the basics regarding generics in Python with plenty of examples.</description><pubDate>Sun, 28 Jul 2024 19:57:38 GMT</pubDate><content:encoded>&lt;p&gt;While hanging out in a Discord server a few days ago, someone brought up the topic of generics in Python. This sparked curiosity among several members, leading to questions about what generics are and how or when to use them. Generics aren&apos;t unique to Python, many programming languages use them as fundamental tools for creating robust and efficient software.&lt;/p&gt;
&lt;p&gt;This conversation got me thinking it would be a great topic to explore together on this blog. So, let&apos;s dive into the basics of using generics in Python, discuss some variants, and discover how they can streamline our code and enhance its reliability.&lt;/p&gt;
&lt;h2&gt;What are generics?&lt;/h2&gt;
&lt;p&gt;Generics are designed to enhance the flexibility and safety of your code. Here&apos;s what they aim to do:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Enable Flexibility&lt;/strong&gt;: They allow functions, methods, and classes to work with different data types while preserving information about the relationships between these data types, such as the consistency between input arguments and return values.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Improve Type Safety&lt;/strong&gt;: Generics help specify how different types can interact more explicitly, reducing errors by clarifying the data types used in operations.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We achieve these benefits by using generic types—placeholders for data types—instead of specific or parent types when defining components. This allows your code to be both flexible and safe, accommodating various data types while ensuring operations are performed correctly.&lt;/p&gt;
&lt;p&gt;If that sounds a bit abstract, don&apos;t worry! The best way to understand generics is to see them in action. Let’s dive into some simple examples to see how you can use generics in your Python projects to make your code more robust and flexible.&lt;/p&gt;
&lt;h2&gt;Using generics in Python&lt;/h2&gt;
&lt;p&gt;I&apos;ll begin by using the traditional approach familiar to most Python developers. This involves utilising &lt;code&gt;TypeVar&lt;/code&gt; from the &lt;code&gt;typing&lt;/code&gt; module. As we progress through the post, I&apos;ll also cover the Type Parameter Syntax introduced by &lt;a href=&quot;https://peps.python.org/pep-0695/&quot;&gt;PEP-695&lt;/a&gt; in Python 3.12. This update brings several enhancements that simplify the declaration and usage of generics, and I’ll highlight some of its key nuances. It is also important to note that Python will not do anything special with the generics, it is dependent on the person running a type checker e.g. Pyright / Pylance, MyPy or Pyre.&lt;/p&gt;
&lt;p&gt;This function accepts a list containing elements of any type and returns the first element. It uses the `` type to indicate that it can handle elements of any type without restriction.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from typing import Any

def get_first_item(items: list[Any]) -&gt; Any:
    return items[0]

a: list[int] = [1, 2, 3]
b: list[str] = [&quot;hello&quot;, &quot;world&quot;]
c: list[list[int]] = [[1, 2], [3, 4, 5]]

print(get_first_item(a))
print(get_first_item(b))
print(get_first_item(c))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So why choose a generic over using &lt;code&gt;Any&lt;/code&gt;? The key advantage of using a generic lies in its ability to ensure consistency and type safety. With a generic, you can guarantee that the type returned by the function matches the type of the elements in the provided list. This consistency is not assured with &lt;code&gt;Any&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from typing import TypeVar

T = TypeVar(&quot;T&quot;)

def get_first_item(items: list[T]) -&gt; T:
    return items[0]

a: list[int] = [1, 2, 3]
b: list[str] = [&quot;hello&quot;, &quot;world&quot;]
c: list[list[int]] = [[1, 2], [3, 4, 5]]

print(get_first_item(a))
print(get_first_item(b))
print(get_first_item(c))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since the specific type of elements list contains is not of concern, rather only that the function returns the same type as provided in the list, we define a generic type named &lt;code&gt;T&lt;/code&gt; with &lt;code&gt;T = TypeVar(&quot;T&quot;)&lt;/code&gt;. This approach replaces the use of &lt;code&gt;Any&lt;/code&gt;, which lacks this level of type specificity.&lt;/p&gt;
&lt;p&gt;By using &lt;code&gt;T&lt;/code&gt;, we introduce type safety to the &lt;code&gt;get_first_item()&lt;/code&gt; function, safeguarding against issues like returning a transformed type or an incorrect literal. This setup ensures that our function&apos;s behaviour remains predictable and consistent, protecting against unintended coding errors.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def get_first_item(items: list[T]) -&gt; T:
    return str(items[0]) # Expression of type &quot;str&quot; is incompatible with return type &quot;T@get_first_item&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&apos;s look at one more simple example before we move on.&lt;/p&gt;
&lt;p&gt;The below will warn that &lt;code&gt;get_first_key()&lt;/code&gt; is expecting to return a key and not a value from the dictionary. But, as mentioned near the start of this post, this will not prevent the code from running and it will still print the first value, which is 1.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from typing import TypeVar

K = TypeVar(&quot;K&quot;)
V = TypeVar(&quot;V&quot;)

def get_first_key(container: dict[K, V]) -&gt; K:
    return list(container.values())[0] # Expression of type &quot;V@get_first&quot; is incompatible with return type &quot;K@get_first&quot;

test: dict[str, int] = {&quot;k&quot;: 1}
print(get_first_key(test))
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Limiting generics to specific types&lt;/h2&gt;
&lt;p&gt;In the previous examples, we utilised generic types that could represent any possible type. But what if we want to narrow down and specifically limit the types that a generic can represent?&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from typing import TypeVar

T = TypeVar(&quot;T&quot;, str, int)

def combine_elements(items: list[T]) -&gt; str:
    return &quot;&quot;.join(str(item) for item in items)

print(combine_elements([&quot;hello&quot;, &quot;world&quot;]))  # Output: helloworld
print(combine_elements([1, 2, 3]))  # Output: 123

print(combine_elements([1.0, 2.0, 3.0])) # Argument of type &quot;list[float]&quot; cannot be assigned to parameter &quot;items&quot; of type &quot;List[T@combine_elements]&quot; in function &quot;combine_elements&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here we constrain &lt;code&gt;T&lt;/code&gt; to only allow &lt;code&gt;str&lt;/code&gt; and &lt;code&gt;int&lt;/code&gt; types. When we try passing a list of floats we see the type checker warn that &lt;code&gt;combine_elements()&lt;/code&gt; is not expecting this type.&lt;/p&gt;
&lt;p&gt;We can also set an upper bound constraint, limiting the generic type to that specific type and any of its subclasses.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from typing import TypeVar

T = TypeVar(&quot;T&quot;, bound=int)

class X(int):
    pass

class Y(int):
    pass

def combine_coords(hour: T, minute: T) -&gt; tuple[T,T]:
    return hour, minute

combine_coords(X(14), Y(50))
combine_coords(X(20), 50)

combine_coords(&quot;10&quot;, &quot;50&quot;) # Argument of type &quot;Literal[&apos;10&apos;]&quot; cannot be assigned to parameter &quot;hour&quot; of type &quot;T@combine_coords&quot; in function &quot;combine_coords&quot; - Type &quot;Literal[&apos;10&apos;]&quot; is incompatible with type &quot;int&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Generics with classes&lt;/h2&gt;
&lt;p&gt;By using generics in classes, developers can design components that are not tied to specific data types, thus increasing the utility and scalability of their code. A prime example is the implementation of data structures, such as lists, queues, and trees, which can hold any type of data but enforce type consistency within an instance. This approach eliminates common bugs that arise from type mismatches and enhances code safety.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from typing import Generic, TypeVar

T = TypeVar(&quot;T&quot;)

class Box(Generic[T]):
    def __init__(self) -&gt; None:
        self._contents: list[T] = []

    def add(self, item: T) -&gt; None:
        self._contents.append(item)

    def get_contents(self) -&gt; list[T]:
        return self._contents

toy_box = Box[str]()
toy_box.add(&quot;Teddy Bear&quot;)
toy_box.add(&quot;Toy Car&quot;)

bolt_box = Box[int]() 
bolt_box.add(10) 
bolt_box.add(20)

bolt_box.add(&quot;Toy Hammer&quot;)

print(toy_box.get_contents())
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We have created a &lt;code&gt;Box&lt;/code&gt; class—a versatile container that can hold various specialised items. In our scenario, we have two types of boxes: a toy box and a bolt box. The toy box is designated for toys, represented as strings, like &quot;Teddy Bear&quot; or &quot;Toy Car&quot;, and must be put away before bedtime. The bolt box is used for storing different sizes of bolts, represented as integers.&lt;/p&gt;
&lt;p&gt;However, we&apos;ve made a mistake by trying to add a &quot;Toy Hammer&quot; to our bolt box. Our type checker, as usual, will alert us to this mistake.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Argument of type &quot;Literal[&apos;Toy Hammer&apos;]&quot; cannot be assigned to parameter &quot;item&quot; of type &quot;int&quot; in function &quot;add&quot; &quot;Literal[&apos;Toy Hammer&apos;]&quot; is incompatible with &quot;int&quot;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;But remember it will not prevent us from adding &quot;Toy Hammer&quot; to the list in Box.&lt;/p&gt;
&lt;h2&gt;Variants of generics&lt;/h2&gt;
&lt;p&gt;There are three variants of generics, which simply refer to different behaviours or specifications that can influence how type checking behaves.&lt;/p&gt;
&lt;h3&gt;Invariance&lt;/h3&gt;
&lt;p&gt;By default, all type variables in Python are invariant. Invariance means that if you have a generic type &lt;code&gt;G[T]&lt;/code&gt;, it cannot accept &lt;code&gt;G[U]&lt;/code&gt;, even if &lt;code&gt;U&lt;/code&gt; is a subclass or superclass of &lt;code&gt;T&lt;/code&gt;. Most mutable containers are invariant. e.g. &lt;code&gt;list&lt;/code&gt; and &lt;code&gt;dict&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For example, &lt;code&gt;list[Animal]&lt;/code&gt; can not be used where &lt;code&gt;list[Cat]&lt;/code&gt; is expected, where &lt;code&gt;Animal&lt;/code&gt; is a superclass of &lt;code&gt;Cat&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Covariance&lt;/h3&gt;
&lt;p&gt;A type variable is covariant if it allows a generic class to be substituted with a subclass. This is typically used for return types, where a method of a generic class promises to return a type &lt;code&gt;T&lt;/code&gt;, and it’s safe to use a type &lt;code&gt;U&lt;/code&gt; such that &lt;code&gt;U&lt;/code&gt; is a subclass of &lt;code&gt;T&lt;/code&gt;. You declare this in the &lt;code&gt;TypeVar&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It&apos;s also important to note that when a generic class is covariant, it is recommended to be immutable / read-only. Some examples of this are &lt;code&gt;typing.Sequence&lt;/code&gt;, &lt;code&gt;typing.FrozenSet&lt;/code&gt; and &lt;code&gt;typing.Union&lt;/code&gt;. Remember this for later in the post.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from typing import Generic, TypeVar

T_co = TypeVar(&quot;T_co&quot;, covariant=True, bound=&quot;Animal&quot;)

class Animal: ...

class Cat(Animal): ...

class Dog(Animal): ...

class Snake: ...

class AnimalShelter(Generic[T_co]):
    def __init__(self, animal: T_co) -&gt; None:
        self.animal = animal
        
def adopt_animal(shelter: AnimalShelter[Animal]) -&gt; Animal:
    return shelter.animal

cat = Cat()
dog = Dog()
snake = Snake()

cat_shelter = AnimalShelter(cat)
dog_shelter = AnimalShelter(dog)
reptile_shelter = AnimalShelter(snake)

adopt_cat = adopt_animal(cat_shelter)
adopt_dog = adopt_animal(dog_shelter)
adopt_snake = adopt_animal(reptile_shelter)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;We define a TypeVar &lt;code&gt;T_co&lt;/code&gt; that is marked as a covariant by setting &lt;code&gt;covariant=True&lt;/code&gt; and bounding it to the &lt;code&gt;Animal&lt;/code&gt; class. This means &lt;code&gt;T_co&lt;/code&gt; can accept any subclass of &lt;code&gt;Animal&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Using &lt;code&gt;T_co&lt;/code&gt;, we create an &lt;code&gt;AnimalShelter&lt;/code&gt; generic class that is initialised with an instance of Animal or its subclasses.&lt;/li&gt;
&lt;li&gt;We define a &lt;code&gt;function adopt_animal()&lt;/code&gt; that takes an &lt;code&gt;AnimalShelter&lt;/code&gt; housing any &lt;code&gt;Animal&lt;/code&gt; and returns the animal.&lt;/li&gt;
&lt;li&gt;We create shelters for different animals, cats, dogs, and hypothetically, snakes. Since snakes are not a subclass of &lt;code&gt;Animal&lt;/code&gt; they will trigger a type error:&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;Argument of type &quot;Snake&quot; cannot be assigned to parameter &quot;animal&quot; of type &quot;T_co@AnimalShelter&quot; in function &quot;__init__&quot; Type &quot;Snake&quot; is incompatible with type &quot;Animal&quot;&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;Contravariance&lt;/h3&gt;
&lt;p&gt;Allows substitution of a type with its supertype. For example, if a function expects a type of &lt;code&gt;Cat&lt;/code&gt;, then it will also accept its supertype, &lt;code&gt;Animal&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We define a type variable &lt;code&gt;T_contra&lt;/code&gt; that is contravariant and bounded by the class &lt;code&gt;Animal&lt;/code&gt;. This setup implies that wherever a specific type like &lt;code&gt;Cat&lt;/code&gt; is expected, any of its supertypes (up to &lt;code&gt;Animal&lt;/code&gt;) can be used instead.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from typing import Generic, TypeVar

T_contra = TypeVar(&quot;T_contra&quot;, contravariant=True, bound=&quot;Animal&quot;)

class Animal:
    def speak(self) -&gt; str:
        return &quot;Some noise&quot;

class Cat(Animal):
    def speak(self) -&gt; str:
        return &quot;Meow&quot;

class Handler(Generic[T_contra]):
    def handle(self, t: T_contra) -&gt; None:
        print(t.speak())

def handle_animal(a: Handler[Cat]) -&gt; None:
    a.handle(Cat())

handler: Handler[Animal] = Handler()
handle_animal(handler) 

cat_handler: Handler[Cat] = Handler()
dog_handler: Handler[Dog] = Handler()
handle_animal(cat_handler) 
handle_animal(dog_handler) # Type parameter &quot;T_contra@Handler&quot; is contravariant, but &quot;Dog&quot; is not a supertype of &quot;Cat&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Generics with protocols&lt;/h2&gt;
&lt;p&gt;In practical applications, it&apos;s often beneficial to use a &lt;code&gt;Protocol&lt;/code&gt; to define an interface. This approach ensures that any subclass implementing the interface will include the necessary methods, as enforced by type checking warnings.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from typing import Generic, TypeVar, Protocol

T_co = TypeVar(&quot;T_co&quot;, covariant=True, bound=&quot;Animal&quot;)
T_contra = TypeVar(&quot;T_contra&quot;, contravariant=True, bound=&quot;Animal&quot;)

class Animal(Protocol):
    def speak(self) -&gt; str: ...

class Cat:
    def speak(self) -&gt; str:
        return &quot;Meow&quot;

class Dog:
    def speak(self) -&gt; str:
        return &quot;Woof&quot;

class Fish:
    def swim(self) -&gt; None: ...
        
class Handler(Generic[T_contra]):
    def handle(self, t: T_contra) -&gt; None:
        print(t.speak())

class AnimalShelter(Generic[T_co]):
    def __init__(self, animal: T_co) -&gt; None:
        self.animal = animal

def adopt_animal(shelter: AnimalShelter[Animal]) -&gt; Animal:
    return shelter.animal


def handle_animal(a: Handler[Cat]) -&gt; None:
    a.handle(Cat())

handler: Handler[Animal] = Handler()
handle_animal(handler) 

cat_handler: Handler[Cat] = Handler()
fish_handler: Handler[Fish] = Handler() # &quot;Fish&quot; is incompatible with protocol &quot;Animal&quot;
handle_animal(cat_handler) 
handle_animal(fish_handler) # Type parameter &quot;T_contra@Handler&quot; is contravariant, but &quot;Fish&quot; is not a supertype of &quot;Cat&quot;

cat = Cat()
dog = Dog()
fish = Fish()
cat_shelter = AnimalShelter(cat)
dog_shelter = AnimalShelter(dog)
fish_shelter = AnimalShelter(fish) # Type &quot;Fish&quot; is incompatible with type &quot;Animal&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the example above, the &lt;code&gt;Fish&lt;/code&gt; class does not implement the &lt;code&gt;speak()&lt;/code&gt; method, therefore it does not meet the requirements of the &lt;code&gt;Animal&lt;/code&gt; protocol. Because of that, you can&apos;t use &lt;code&gt;Fish&lt;/code&gt; in situations where you need something that fits the &lt;code&gt;Animal&lt;/code&gt; protocol, whether it&apos;s covariant or contravariant.&lt;/p&gt;
&lt;h2&gt;Python 3.12 Generics (PEP 695)&lt;/h2&gt;
&lt;p&gt;With PEP 695, we can streamline how generics are defined. Let&apos;s take our very first example.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from typing import TypeVar

T = TypeVar(&quot;T&quot;)

def get_first_item(items: list[T]) -&gt; T:
    return items[0]
This would simply become

def get_first_item[T](items: list[T]) -&gt; T:
    return items[0]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note how we did not need to import and define the &lt;code&gt;TypeVar&lt;/code&gt; for each generic. How about with a &lt;code&gt;Protocol&lt;/code&gt;?&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from typing import Protocol, TypeVar


C1 = TypeVar(&quot;C1&quot;)
C2 = TypeVar(&quot;C2&quot;) 

class CurrencyExchangeMapper(Protocol[C1, C2]):
    def to(self, amount: C1) -&gt; C2: ...
    def from_(self, amount: C2) -&gt; C1: ...

class USDGBPExchangeMapper(CurrencyExchangeMapper[float, float]):
    def __init__(self, exchange_rate: float, reverse_rate: float) -&gt; None:
        self.exchange_rate = exchange_rate
        self.reverse_rate = reverse_rate

    def to(self, amount: float) -&gt; float:
        return amount * self.exchange_rate

    def from_(self, amount: float) -&gt; float:
        return amount * self.reverse_rate

exchange_mapper = USDGBPExchangeMapper(exchange_rate=0.777255, reverse_rate=1.28658)
print(f&quot;100 USD is {exchange_mapper.to(100):.2f} GBP&quot;) 
print(f&quot;100 GBP is {exchange_mapper.from_(100):.2f} USD&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The only part that changes is the &lt;code&gt;CurrencyExchangeMapper&lt;/code&gt; class.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class CurrencyExchangeMapper[C1, C2](Protocol):
    def to(self, amount: C1) -&gt; C2: ...
    def from_(self, amount: C2) -&gt; C1: ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Setting bounds and limiting types in PEP 695 are similar, but there is an important difference. With the enhancements introduced in PEP 695, type variance does not always need to be explicitly defined because it can often be inferred from the context.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from typing import Sequence

class DataBundle[T, B: Sequence[bytes], S: (int, str)]:
    def __init__(self, general: T, binary_data: B, identifier: S) -&gt; None:
        self.general = general
        self.binary_data = binary_data
        self.identifier = identifier
        
# Pre 3.12

from typing import TypeVar, Generic, Sequence

T = TypeVar(&quot;T&quot;, contravariant=True)
B = TypeVar(&quot;B&quot;, bound=Sequence[bytes], covariant=True)
S = TypeVar(&quot;S&quot;, int, str)

class OldDataBundle(Generic[T, B, S]):
    def __init__(self, general: T, binary_data: B, identifier: S) -&gt; None:
        self.general = general
        self.binary_data = binary_data
        self.identifier = identifier
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Recall when I said to remember that covariants are recommended to be immutable / read-only? Well here is an example showing how PEP 695 is enforcing this with class generics. I personally find this stricter approach to be more correct, but it is a differing behaviour to &lt;code&gt;class Thing(Generic[T])&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Note how this does not warn of any errors.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from typing import Generic, TypeVar

T_co = TypeVar(&quot;T_co&quot;, bound=&quot;Animal&quot;, covariant=True)

class Animal:
    pass

class Cat(Animal):
    pass

class AnimalShelter(Generic[T_co]):
    def __init__(self, animal: T_co) -&gt; None:
        self.animal = animal

def adopt_animal(box: AnimalShelter[Animal]) -&gt; Animal:
    return box.animal
    
cat = Cat()
rescue_cat = AnimalShelter(cat) 
my_cat = adopt_animal(rescue_cat)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now if we convert this to the PEP 695 way&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class Animal:
    pass

class Cat(Animal):
    pass

class AnimalShelter[T_co: Animal]:
    def __init__(self, animal: T_co) -&gt; None:
        self.animal = animal

def adopt_animal(box: AnimalShelter[Animal]) -&gt; Animal:
    return box.animal

cat = Cat()
rescue_cat = AnimalShelter(cat) 
my_cat = adopt_animal(rescue_cat) # &quot;AnimalShelter[Cat]&quot; is incompatible with &quot;AnimalShelter[Animal]&quot;
 Type parameter &quot;T_co@AnimalShelter&quot; is invariant, but &quot;Cat&quot; is not the same as &quot;Animal&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because of inference, it deems &lt;code&gt;AnimalShelter[Animal]&lt;/code&gt; to be invariant as &lt;code&gt;self.animal&lt;/code&gt; is mutable. There are two ways to resolve this.&lt;/p&gt;
&lt;p&gt;The first is by using &lt;code&gt;typing.Final&lt;/code&gt;. This informs the type checker that the name cannot be re-assigned or overridden in a subclass.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from typing import Final

class Animal:
    pass

class Cat(Animal):
    pass

class AnimalShelter[T_co: Animal]:
    def __init__(self, animal: T_co) -&gt; None:
        self.animal: Final = animal
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The second, and the way I believe is more correct, is by making &lt;code&gt;self._animal&lt;/code&gt; private and using a &lt;code&gt;@property&lt;/code&gt;, essentially making it &quot;read-only&quot;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class Animal:
    pass

class Cat(Animal):
    pass

class AnimalShelter[T_co: Animal]:
    def __init__(self, animal: T_co) -&gt; None:
        self._animal = animal
        
    @property
    def animal(self) -&gt; Animal:
        return self._animal

def adopt_animal(box: AnimalShelter[Animal]) -&gt; Animal:
    return box.animal

cat = Cat()
rescue_cat = AnimalShelter(cat) 
my_cat = adopt_animal(rescue_cat)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Final thoughts&lt;/h2&gt;
&lt;p&gt;Both approaches have their place and choosing which way you want to handle your generics in Python will essentially come down some key points.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Project&lt;/strong&gt; and team scale: Larger, more complex projects might still benefit from the explicit nature of traditional generics, particularly for maintaining strict type control across large teams.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Codebase Maturity&lt;/strong&gt;: Introducing new syntax in a mature codebase requires careful consideration, training, and potential refactoring. New projects have the freedom to adopt the latest features from the start.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Development Philosophy&lt;/strong&gt;: Some teams prioritise using the latest Python features to keep their codebase modern and concise, while others value the stability and clarity brought by the established patterns.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Personal project&lt;/strong&gt;: Use whatever you want.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Thanks for reading and if you wish to dig further into generics in Python check out the following resources:&lt;/p&gt;
&lt;p&gt;https://peps.python.org/pep-0484/#generics&lt;/p&gt;
&lt;p&gt;https://peps.python.org/pep-0695/&lt;/p&gt;
&lt;p&gt;https://mypy.readthedocs.io/en/stable/generics.html&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item></channel></rss>