public synchronized GOTreeNode getLCA(GOTreeNode annotation) {
        GOTreeNode lca = cachedLCAs.get(annotation);
        if (lca == null) {
            lca = goTree.getRoot();
            int lcaDepth = goTree.getRoot().getDepth();
            int distanceLCAToAnnotation = lca.getDistanceToAnnotation(annotation);
            Queue<GOTreeNode> pending = new ArrayDeque<GOTreeNode>();
            pending.add(this);

            while (!pending.isEmpty()) {
                GOTreeNode node = pending.poll();
                if (node.getDepth() > lcaDepth) {
                    if (node.getAnnotations().contains(annotation)) {
                        lca = node;
                        lcaDepth = node.getDepth();
                        distanceLCAToAnnotation = node.getDistanceToAnnotation(annotation);
                    }
                    for (GOTreeNode ancestor : node.getAncestors()) {
                        if (ancestor.getDepth() >= lcaDepth) {
                            pending.add(ancestor);
                        }
                    }
                }
                else if (node.getDepth() == lcaDepth) {
                    if (node.getAnnotations().contains(annotation)) {
                        int distanceLCAToCandidateAnnotation = node.getDistanceToAnnotation(annotation);
                        if (distanceLCAToCandidateAnnotation < distanceLCAToAnnotation) {
                            lca = node;
                            lcaDepth = node.getDepth();
                            distanceLCAToAnnotation = distanceLCAToCandidateAnnotation;
                        }
                    }
                }
            }
        }
        cachedLCAs.put(annotation, lca);
        return lca;
    }