📁 Sangam Dhakal - Portfolio v2.0 - Netscape Navigator 4.0
_
□
×

πŸ“š Interview question

What are trees, binary trees and binary search trees? Explain traversals, why balance matters, and where trees appear in real .NET systems and databases.

Tree terminology, binary trees and BSTs, in/pre/post/level-order traversals, balanced trees (red-black, AVL), B-trees in database indexes, and real-world trees in .NET.

Mid-Level Theory Intermediate b-tree binary-tree bst data-structures recursion tree

🎯 Key points

  • A tree is a hierarchy with one root, parent-child edges and no cycles; a binary tree allows at most two children per node
  • A binary search tree keeps left subtree < node < right subtree, so search, insert and delete cost O(height)
  • A balanced tree has O(log n) height; inserting sorted data into a plain, non-self-balancing BST makes it a linked list with O(n) height
  • Self-balancing trees restore balance with rotations: AVL keeps subtree heights within 1 (faster lookups, more rotations); red-black trees use colour rules to keep height ≤ 2 log₂(n + 1) (fewer rotations) and back .NET's SortedSet<T> and SortedDictionary<TKey, TValue>
  • Traversals: in-order gives sorted output, pre-order copies or serialises, post-order deletes or evaluates, level-order (BFS) works level by level; depth-first uses O(height) stack, breadth-first O(width) queue
  • Database indexes are B+ trees: wide pages with hundreds of keys, all leaves at the same depth and linked for range scans, so 100 million rows need only 3–4 levels
  • Build hierarchies from flat parent-id rows in O(n) with a dictionary, and traverse iteratively when depth is not under your control
  • Trees appear throughout .NET: Roslyn syntax trees, expression trees translated by EF Core, JsonNode and XDocument, and configuration sections

⏱ 30-second answer

A tree is a hierarchy of nodes with one root, where each node has children and no cycles. A binary tree limits each node to two children (left, right). A binary search tree (BST) adds an ordering rule: left subtree < node < right subtree, so search, insert and delete take O(height).

If the tree is balanced, height is O(log n); if you insert sorted data into a plain BST it degenerates into a linked list with O(n) height. Self-balancing trees (red-black, AVL) keep O(log n); .NET's SortedSet and SortedDictionary are red-black trees.

Traversals: in-order (sorted output for a BST), pre-order (copy/serialise), post-order (delete/evaluate), level-order (BFS).

Trees are everywhere: database B+ tree indexes, file systems, JSON/XML/DOM, Roslyn syntax trees, expression trees, org charts.

πŸ—£ Full answer (senior)

Terminology

                 [50]            ← root (depth 0)
               /      \
           [30]        [70]      ← internal nodes
           /  \        /   \
        [20] [40]   [60]  [80]   ← leaves (depth 2)

height = 2, n = 7, BST property holds
Term Meaning
Root / leaf Top node / node with no children
Depth / height Edges from root / longest path to a leaf
Full binary tree Every node has 0 or 2 children
Complete binary tree All levels full except possibly the last, filled left to right (heaps use this)
Balanced Heights of subtrees differ by a bounded amount, so height is O(log n)

BST costs

Operation Balanced Degenerate (sorted inserts)
Search / insert / delete O(log n) O(n)
Min / max O(log n) O(n)
In-order traversal O(n) O(n)

Traversals

Order Visit sequence for the tree above Use
In-order (L, N, R) 20 30 40 50 60 70 80 Sorted output
Pre-order (N, L, R) 50 30 20 40 70 60 80 Copy, serialise
Post-order (L, R, N) 20 40 30 60 80 70 50 Delete, evaluate expressions
Level-order (BFS) 50 30 70 20 40 60 80 Shortest depth, printing by level

B-trees in databases

SQL Server, PostgreSQL and most databases store indexes as B+ trees: each node is a page (8 KB in SQL Server) holding hundreds of keys, so the tree is very wide and shallow. A table with 100 million rows typically has an index only 3–4 levels deep, so a seek reads 3–4 pages.

Production example

A category tree for an e-commerce site (12,000 categories, up to 9 levels) was loaded by calling the database once per node to fetch its children: 12,000 queries per cache rebuild. We loaded all rows in one query, built the tree in memory with a Dictionary<int, Node> keyed by ID (O(n)), and computed breadcrumbs with a parent walk. The rebuild went from 45 seconds to 200 ms.

πŸ” What the interviewer is checking

Checks tree fundamentals and the ability to connect them to real systems.

Green flags

  • Correct BST property and O(height) costs.
  • Explains degeneration and how self-balancing trees use rotations or colouring.
  • Knows the four traversals and when to use each.
  • Connects to B+ tree indexes and .NET (SortedSet<T>, expression and syntax trees).

Red flags

  • Says BST operations are always O(log n).
  • Cannot describe any traversal.

πŸ“š Learn in depth

1. Why balance matters, and how it is kept

Insert 10, 20, 30, 40, 50 into a plain BST:

[10]
   \
   [20]
      \
      [30]          height = n - 1 β†’ every operation O(n)
         \
         [40]
            \
            [50]

Self-balancing trees fix this with rotations, which re-hang nodes in O(1) while keeping the BST order:

Left rotation at 10 (right-heavy):

  [10]                 [20]
     \                /    \
     [20]    ──►   [10]    [30]
        \
        [30]
Tree Balance rule How it rebalances Notes
AVL Subtree heights differ by ≀ 1 at every node Tracks a height or balance factor; single or double rotations after insert/delete Shallower, so faster lookups; more rotations on writes
Red-black Every node red or black; no red node has a red child; every root-to-leaf path has the same number of black nodes Recolouring plus at most 2 rotations per insert, 3 per delete Height ≀ 2 logβ‚‚(n + 1); used by .NET SortedSet<T> and SortedDictionary, Java TreeMap, and the Linux scheduler's run queue
B-tree / B+ tree Many keys per node, all leaves at the same depth Split full nodes upwards, merge or borrow on delete Disk- and page-friendly: database indexes, file systems

2. B+ tree shape

                     [ 100 | 500 | 900 ]                  root page
             /            |           |          \
   [10|40|70]       [200|300|400]  [600|700|800]  [950|...]  intermediate pages
      ...                ...            ...           ...
   leaf pages hold keys + row locators, linked left↔right for range scans

Leaves are linked, so WHERE OrderDate BETWEEN ... seeks once then scans sideways. This is why index key order and "sargable" predicates matter.

3. Traversal costs

Traversal Time Extra memory Practical use
In-order O(n) O(height) stack Sorted output from a BST, range reports
Pre-order O(n) O(height) stack Serialising a tree, copying, printing an outline (folders, JSON)
Post-order O(n) O(height) stack Deleting children before parents, evaluating expression trees, computing folder sizes
Level-order O(n) O(width) queue Breadcrumb levels, shortest depth, rendering by level

4. Trees in .NET

  • Roslyn syntax trees (SyntaxTree, SyntaxNode) for analysers and source generators.
  • Expression trees (Expression<Func<T, bool>>) that EF Core walks and translates to SQL.
  • JSON and XML (JsonNode, JsonDocument, XDocument), and the HTML DOM.
  • Configuration hierarchies (IConfiguration sections); DI registrations form a dependency graph (a DAG rather than a tree).
  • SQL Server hierarchyid and adjacency-list tables (ParentId) for org charts and categories, loaded with recursive CTEs.

5. Recursion versus iteration

Recursive traversals are elegant, but call depth equals tree height. For degenerate or user-supplied trees, use an explicit stack (DFS) or queue (BFS) to avoid StackOverflowException, which cannot be caught.

6. Classic interview problems

  • Max depth, check balanced, validate a BST (track min/max bounds, not just the parent).
  • Lowest common ancestor.
  • Serialise and deserialise a tree.
  • Level-order traversal, zigzag order.
  • Build a tree from parent pointers in O(n) with a dictionary.

7. Try it yourself

  1. Insert 1–10,000 in order into a simple BST and measure height; then insert shuffled values.
  2. Compare with SortedSet<int> timings.
  3. In SQL Server, run SET STATISTICS IO ON and query by an indexed column; note logical reads (about the index depth).
  4. Write in-order, pre-order and post-order traversals and print the results for a small tree.

πŸ’» Code

public sealed class Node(int value)
{
    public int Value { get; } = value;
    public Node? Left { get; set; }
    public Node? Right { get; set; }
}

// BST insert and search: O(height)
public static Node Insert(Node? root, int value)
{
    if (root is null) return new Node(value);
    if (value < root.Value) root.Left = Insert(root.Left, value);
    else if (value > root.Value) root.Right = Insert(root.Right, value);
    return root;
}

public static bool Contains(Node? node, int value)
{
    while (node is not null)
    {
        if (value == node.Value) return true;
        node = value < node.Value ? node.Left : node.Right;
    }
    return false;
}

// Validate a BST with bounds (checking only parent/child is a classic bug)
public static bool IsValidBst(Node? node, long min = long.MinValue, long max = long.MaxValue) =>
    node is null ||
    (node.Value > min && node.Value < max &&
     IsValidBst(node.Left, min, node.Value) &&
     IsValidBst(node.Right, node.Value, max));

// In-order traversal without recursion
public static IEnumerable<int> InOrder(Node? root)
{
    var stack = new Stack<Node>();
    var current = root;
    while (current is not null || stack.Count > 0)
    {
        while (current is not null) { stack.Push(current); current = current.Left; }
        current = stack.Pop();
        yield return current.Value;
        current = current.Right;
    }
}
// Build a category tree from flat rows in O(n)
public sealed record CategoryRow(int Id, int? ParentId, string Name);
public sealed class Category(int id, string name)
{
    public int Id { get; } = id;
    public string Name { get; } = name;
    public List<Category> Children { get; } = [];
}

public static List<Category> BuildTree(IReadOnlyList<CategoryRow> rows)
{
    var byId = rows.ToDictionary(r => r.Id, r => new Category(r.Id, r.Name));
    var roots = new List<Category>();
    foreach (var row in rows)
    {
        if (row.ParentId is int parentId) byId[parentId].Children.Add(byId[row.Id]);
        else roots.Add(byId[row.Id]);
    }
    return roots;
}

⚠ Common mistakes

  • Mistake: Assuming a BST is always O(log n). Why it is wrong: Inserting sorted or nearly sorted data into a BST without self-balancing makes it a linked list with O(n) operations. Instead: Use a self-balancing tree (SortedSet<T>, SortedDictionary<TKey, TValue>), or shuffle inputs when building simple trees.

  • Mistake: Validating a BST by comparing each node only with its direct children. Why it is wrong: A deeper node can break the rule against an ancestor while every parent-child pair looks fine. Instead: Pass down min/max bounds as you recurse.

  • Mistake: Loading hierarchical data one level per query. Why it is wrong: It causes N+1 queries, one per node, and rebuilds become slow and hammer the database. Instead: Load all rows (or use a recursive CTE) and build the tree in memory with a dictionary in O(n).

  • Mistake: Recursing over deep or user-supplied trees. Why it is wrong: Stack depth equals tree height, and a stack overflow kills the process. Instead: Use iterative traversal with an explicit stack or queue.

  • Mistake: Confusing binary trees with B-trees when reasoning about indexes. Why it is wrong: A binary tree over 100 million rows would be about 27 levels deep; a B+ tree is 3–4, which is why seeks are cheap. Instead: Think of indexes as wide, shallow page trees with linked leaves for range scans.

πŸ› Senior insight: trade-offs

Trees matter less as something I implement and more as a mental model: database indexes (B+ trees), ordered collections (red-black trees), syntax and expression trees, and every hierarchical domain model. Knowing that an index seek is a walk down a shallow B+ tree, or that SortedDictionary is O(log n) because it is a balanced tree, lets me predict performance without benchmarking everything.

Need Structure I reach for Why
Ordered in-memory data with frequent changes SortedSet<T> / SortedDictionary Red-black tree, O(log n) guaranteed
Persistent ordered lookups and ranges Database B+ tree index Few page reads, linked leaves for ranges
Hierarchical domain data (categories, org chart) Adjacency list in SQL + in-memory tree built in O(n) Simple writes, fast reads once cached
Code analysis or query translation Roslyn syntax trees, expression trees Tooling already provides traversal and rewriting
Prefix search Trie or a database prefix index Cost depends on key length, not item count

The recurring production issues are balance (degenerate structures) and access patterns for hierarchies (N+1 loading, deep recursion). I load hierarchies in bulk, build them with dictionaries in O(n), and traverse iteratively when depth is not under my control.

πŸ”— Follow-ups the interviewer may ask

  1. When: Candidate mentions complete binary trees Why: Moves to heaps
    How to answer

    A heap is a complete tree stored in an array that keeps the heap property: in a min-heap every parent ≀ its children, so the smallest item is always at the root.

    • Peek min: O(1). Insert (sift up) and remove min (sift down): O(log n). Build from n items: O(n).
    • Not sorted overall, and not searchable quickly (finding an arbitrary item is O(n)).

    .NET's PriorityQueue<TElement,TPriority> (.NET 6+) is an array-backed min-heap (4-ary internally). Enqueue, Dequeue, TryPeek, EnqueueDequeue; not stable for equal priorities; no decrease-key (Remove was added in .NET 9). For a max-heap, pass a reversed comparer.

    Top K from a stream: keep a min-heap of size K; for each item, if it beats the heap's minimum, replace it. That is O(n log K) time and O(K) memory.

    (Not to be confused with the managed heap, which is memory.)

  2. When: Candidate mentions prefix searches Why: Probes tries
    How to answer

    A trie is a tree where each edge is a character and each path from the root spells a prefix. Nodes mark where a complete word ends.

    • Insert and exact search: O(L) where L is the word length, independent of how many words are stored.
    • Prefix search ("all words starting with syd"): walk L nodes, then collect the subtree.

    Great for autocomplete, spell-check, word games and longest-prefix matching (IP routing). Downside: memory (one node per character, plus child maps). Compressed (radix) tries merge single-child chains.

    Alternatives: a sorted array + binary search for prefixes (compact, often fast enough), a database LIKE 'syd%' on an index, or a search engine's suggesters for fuzzy, ranked autocomplete at scale.

  3. When: Candidate says a tree is a special graph Why: Moves to general graphs
    How to answer

    A graph is a set of vertices (nodes) connected by edges. Edges can be directed or undirected, weighted or not, and the graph may contain cycles. Trees are a special case (connected, no cycles).

    Representations:

    • Adjacency list (Dictionary<TNode, List<TNode>>): O(V + E) memory, best for sparse graphs (most real ones).
    • Adjacency matrix (bool[V,V]): O(VΒ²) memory, O(1) edge check, for small dense graphs.
    • Edge list: simple, good for storage and some algorithms.

    Backend graphs: service and package dependencies, DI containers, build systems (project references), workflow/job DAGs, permission inheritance, routing, social links.

    For dependencies you need a DAG: find an execution order with topological sort (Kahn's algorithm, O(V + E)); if not all nodes can be ordered, there is a cycle.

πŸ›  Real-world problems: how you handle them

Category tree rebuild with 12,000 queries
Situation
[E-commerce site] on ASP.NET Core [10] caching a category tree of [12,000] categories up to [9] levels deep.
Signals
Cache rebuild took [45 s] and spiked database load every [10 minutes]; pages served stale categories during rebuilds.
Investigation
SQL profiling showed one query per node to load its children (N+1 recursion).
Resolution
Loaded all categories in one query and built the tree in memory with a `Dictionary<int, Category>` in O(n); breadcrumbs computed via parent links.
Prevention
Query count budget test for the rebuild; guideline for loading hierarchies (single query or recursive CTE).

How to tell it (STAR)

**S:** Rebuilding our [category tree] cache took [45 seconds] and hammered the database. **T:** I was asked to make it fast and cheap. **A:** Profiling showed [12,000] queries, one per node. I loaded all rows in one query and built the tree in memory with a dictionary keyed by ID, linking children to parents in a single pass. **R:** The rebuild dropped to [200 ms], database load from it disappeared, and we added a query-count test so it stays that way.

↩ Usually asked after

More in Data Structures

Last updated 23 Oct 2026.

🌐 Sangam's Webring

βͺ Previous 🏠 Main Next ⏩
πŸ’¬ Chat with me