The Binary Tree Simulator is the most algorithm-heavy tool on RedoHub, and it's the one where I most deliberately chose the "obviously simple" approach over the "obviously correct" one in a few places. Here's what that actually looked like in the code.

No Canvas, No SVG, Just Absolutely Positioned Divs

My first instinct for a tree visualizer is usually to reach for <canvas> or SVG, since that's what most tree-drawing tutorials use. I ended up with neither. Every node is a plain <div class="node">, absolutely positioned with pixel coordinates I compute myself, and every edge is a 2px-tall <div> rotated with CSS transform:

const deltaX = node.left.x - node.x; const deltaY = node.left.y - node.y; const length = Math.sqrt(deltaX * deltaX + deltaY * deltaY); const angle = Math.atan2(deltaY, deltaX) * 180 / Math.PI; edge.style.width = length + 'px'; edge.style.transform = `rotate(${angle}deg)`;

Using atan2 to turn a start point and an end point into a line's length and rotation angle is a trick that's more commonly seen in canvas or SVG code, but it works identically on a plain HTML element. The advantage is that every node stays a real, clickable, hoverable DOM element with normal CSS hover states and transitions -- no manual hit-testing math to figure out which node the mouse is over, which canvas-based approaches need. The cost is that drawing hundreds of nodes this way would be slower than canvas, but a learning tool for binary trees is never going to have hundreds of nodes on screen at once, so that ceiling never mattered.

The Tree Is Recomputed and Redrawn From Scratch, Every Time

There's no incremental update anywhere in drawTree(). Insert one node, and the function clears the entire canvas and rebuilds every position, every edge, and every node element from the root down:

function drawTree() { const canvas = document.getElementById('treeCanvas'); canvas.innerHTML = ''; ... calculateNodePositions(bst.root, centerX, startY, horizontalSpacing); drawEdges(bst.root, treeContent); drawNodes(bst.root, treeContent); }

A more "correct" implementation would diff the old tree shape against the new one and only touch the nodes that actually moved. I skipped that entirely. Recomputing everything from the root is a handful of recursive function calls -- trivial for a tool where the tree realistically never exceeds a few dozen nodes -- and it completely sidesteps an entire class of bugs where a stale DOM node from a previous state doesn't get cleaned up correctly. The one real cost is that clicking a node to highlight it has to happen after the redraw, using a fresh querySelectorAll('.node') call, because the DOM element that existed before the redraw is already gone.

The Balance Button Doesn't Rebalance -- It Rebuilds

"Balance" sounds like it should trigger AVL-style rotations, but that's not what happens. Instead, it takes an inorder traversal of the current tree (which is always sorted, by definition of a BST), throws the whole tree away, and reinserts the values by always picking the middle element of the remaining range first:

function insertBalanced(arr, start, end) { if (start > end) return; const mid = Math.floor((start + end) / 2); bst.insert(arr[mid]); insertBalanced(arr, start, mid - 1); insertBalanced(arr, mid + 1, end); }

Inserting the median first, then recursively the medians of each half, is the classic way to turn a sorted array into a height-balanced BST in one pass, and it's a genuinely elegant fit for a demo tool. The trade-off I accepted is that the tree isn't continuously self-balancing the way a real AVL or Red-Black tree would be during normal inserts -- if you keep inserting after balancing, the tree can drift back toward being lopsided, and "Balance" is something you click again rather than something that happens automatically. For a teaching tool, I think that's actually clearer: it lets someone insert values, watch the tree get unbalanced, then press one button and watch it become balanced, as two distinct, visible states.

Insert Checks for Duplicates Explicitly, Even Though It Doesn't Need To

A textbook BST insert() doesn't need a duplicate check -- if a value already exists, walking left-or-right by comparison naturally lands on the existing node and nothing happens. I added an explicit check anyway:

insert(value) { if (this.search(value)) { return false; // Value already exists } this.root = this.insertNode(this.root, value); this.nodeCount++; return true; }

The reason is purely about user feedback, not correctness. Without the check, inserting a duplicate would silently do nothing and the person interacting with the tool would have no idea why their click didn't seem to work. The explicit search() call costs an extra O(log n) traversal on every insert, which is irrelevant at this scale, in exchange for being able to show a specific "Node already exists!" toast instead of a confusing non-event.

The One Bug I Know About and Left In

The isAnimating flag blocks a second search from starting while one is already animating, but it does nothing to stop someone from clicking Insert or Delete during a search animation. Because drawTree() tears down and rebuilds the entire DOM, an insert that lands mid-animation can pull the rug out from under the search's node references. It's a real bug, and I know exactly why it happens -- I just haven't decided it's worth adding a second, more invasive lock for a low-traffic demo tool where the failure mode is "the animation looks a bit odd," not lost data. The honest fix would be disabling every other control button for the duration of the search animation, not just guarding the search button itself, and that's the next thing I'd change if this tool saw heavier use.