Java
Programming
Data Structures
Tree Structure
Software Development

Implementing a dynamic tree structure in java

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

A dynamic tree is a tree structure that can change at runtime: nodes can be inserted, removed, moved, or searched without rebuilding the whole structure. In Java, the practical implementation is usually a mutable node class with parent and child references, plus a few rules that keep the tree consistent.

Core Sections

Decide what the node must store

Most Java tree implementations need at least four things:

  • the node’s value
  • a link to the parent
  • a collection of children
  • operations that preserve the parent-child relationship

A minimal generic node class looks like this:

java
1import java.util.ArrayList;
2import java.util.Collections;
3import java.util.List;
4import java.util.Objects;
5
6public class TreeNode<T> {
7    private final T value;
8    private TreeNode<T> parent;
9    private final List<TreeNode<T>> children = new ArrayList<>();
10
11    public TreeNode(T value) {
12        this.value = Objects.requireNonNull(value);
13    }
14
15    public T getValue() {
16        return value;
17    }
18
19    public TreeNode<T> getParent() {
20        return parent;
21    }
22
23    public List<TreeNode<T>> getChildren() {
24        return Collections.unmodifiableList(children);
25    }

Using a generic type lets the same structure represent files, categories, UI widgets, or parsed document elements.

Add and remove children safely

The important part is not the field definitions. It is enforcing consistency. If a child is added to a new parent, the old parent reference must be cleaned up first.

java
1    public void addChild(TreeNode<T> child) {
2        Objects.requireNonNull(child);
3        if (child == this) {
4            throw new IllegalArgumentException("A node cannot be its own child");
5        }
6        if (isAncestorOf(child)) {
7            throw new IllegalArgumentException("Cannot create a cycle");
8        }
9        if (child.parent != null) {
10            child.parent.children.remove(child);
11        }
12        child.parent = this;
13        children.add(child);
14    }
15
16    public void removeChild(TreeNode<T> child) {
17        if (children.remove(child)) {
18            child.parent = null;
19        }
20    }
21
22    private boolean isAncestorOf(TreeNode<T> node) {
23        TreeNode<T> current = this;
24        while (current != null) {
25            if (current == node) {
26                return true;
27            }
28            current = current.parent;
29        }
30        return false;
31    }
32}

The cycle check matters. A tree that accidentally points back to one of its ancestors stops being a tree and becomes a graph with broken traversal assumptions.

A dynamic tree is not useful unless callers can walk it. Depth-first traversal is a simple default because it mirrors the hierarchical nature of a tree.

java
1public static <T> TreeNode<T> findFirst(TreeNode<T> root, T target) {
2    if (root.getValue().equals(target)) {
3        return root;
4    }
5    for (TreeNode<T> child : root.getChildren()) {
6        TreeNode<T> found = findFirst(child, target);
7        if (found != null) {
8            return found;
9        }
10    }
11    return null;
12}

If the tree changes frequently and lookups must be fast, you can maintain a side index such as a Map from ids to nodes. That improves search speed at the cost of keeping two data structures consistent.

Moving subtrees is just controlled reparenting

One advantage of a dynamic tree is that whole branches can move.

java
1public static <T> void moveSubtree(TreeNode<T> node, TreeNode<T> newParent) {
2    if (node.getParent() != null) {
3        node.getParent().removeChild(node);
4    }
5    newParent.addChild(node);
6}

This is useful for drag-and-drop UIs, folder-like structures, and rule engines that reorganize data at runtime.

Choose collections and invariants deliberately

An ArrayList is a good default for children when insertion order matters and most operations are append, iterate, or indexed access. If you need keyed children, a Map can be more appropriate. If the tree is accessed from multiple threads, do not make the node class “sort of thread-safe.” Either protect it with clear synchronization rules or keep it single-threaded.

Also decide whether values are unique. The tree structure itself does not require uniqueness, but some APIs become ambiguous if many nodes can contain the same value.

Common Pitfalls

  • Updating the child list without updating the child’s parent reference, which leaves the tree internally inconsistent.
  • Allowing a node to become its own ancestor, which silently turns the tree into a cyclic graph.
  • Returning the mutable children list directly, which lets callers bypass invariants and corrupt the structure.
  • Assuming value-based search is cheap even when the tree is large and frequently traversed without an index.
  • Mixing thread access patterns without a clear locking or ownership model, which leads to hard-to-reproduce corruption bugs.

Summary

  • A practical dynamic tree in Java is usually a mutable node structure with parent and child references.
  • The real implementation work is enforcing invariants during add, remove, and move operations.
  • Traversal and search should match the workload, not just the simplest code sample.
  • Prevent cycles explicitly so the structure remains a valid tree.
  • Keep mutability boundaries clear, especially if the structure may be shared across threads.

Course illustration
Course illustration

All Rights Reserved.