Tarjan's strongly connected components algorithm Can anyone suggest to me how I can make this "nicer"? If you can see any glaring details that I could change to speed things up too, that would be great -- though it's not priority.

public class Tarjans {

    private static class Node {
        // -1, -1, our node is unvisited
        // by default.
        public int index = -1, lowLink = -1;
        public String name;

        public Node(String name) {
            this.name = name;
        }

        public String toString() {
            return name;
        }
    }

    // graph representation
    HashMap&lt;String, Node&gt; nodes = new HashMap&lt;&gt;();
    HashMap&lt;String, ArrayList&lt;Node&gt;&gt; graph = new HashMap&lt;&gt;();

    private int index = 0;

    // for a DFS, basically
    private ArrayDeque&lt;Node&gt; visited = new ArrayDeque&lt;&gt;();
    private HashSet&lt;String&gt; stack = new HashSet&lt;&gt;();

    // stores our strongly connected componts
    // from the algorithm pass
    private HashSet&lt;HashSet&lt;Node&gt;&gt; scc = new HashSet&lt;&gt;();

    public HashSet&lt;HashSet&lt;Node&gt;&gt; tarjan() {
        for (Node n : nodes.values()) {
            if (n != null &amp;&amp; n.index == -1) {
                strongConnect(n);
            }
        }
        return scc;
    }

    private void strongConnect(Node node) {
        node.index = index;
        node.lowLink = index;
        index += 1;

        visited.push(node);
        stack.add(node.name);

        ArrayList&lt;Node&gt; neighbours = graph.get(node.name);
        if (neighbours != null) {
            neighbours.forEach(n -&gt; {
                if (n.index == -1) {
                    strongConnect(n);
                    node.lowLink = Math.min(node.lowLink, n.lowLink);
                }
                else if (stack.contains(n.name)) {
                    node.lowLink = Math.min(node.lowLink, n.index);
                }
            });
        }

        if (node.lowLink == node.index) {
            HashSet&lt;Node&gt; cycle = new HashSet&lt;&gt;();
            while (true) {
                Node p = visited.pop();
                stack.remove(p.name);
                cycle.add(p);

                if (p == node) {
                    break;
                }
            }
            if (cycle.size() &gt; 1) {
                scc.add(cycle);         
            }
        }
    }

    private void foo() {
        nodes.put("bs", new Node("bs"));
        nodes.put("a", new Node("a"));
        nodes.put("b", new Node("b"));
        nodes.put("c", new Node("c"));

        graph.put("bs", new ArrayList&lt;&gt;(Arrays.asList(nodes.get("a"))));
        graph.put("a", new ArrayList&lt;&gt;(Arrays.asList(nodes.get("b"))));
        graph.put("b", new ArrayList&lt;&gt;(Arrays.asList(nodes.get("c"))));
        graph.put("c", new ArrayList&lt;&gt;(Arrays.asList(nodes.get("a"))));

        HashSet&lt;HashSet&lt;Node&gt;&gt; cycles = tarjan();
        for (HashSet&lt;Node&gt; cycle : cycles) {
            System.out.println("[" + cycle.stream().map(Node::toString).collect(Collectors.joining(",")) + "]");
        }
    }

    public static void main(String[] args) {
        new Tarjans().foo();
    }

}
There is no reason to use String as keys in graph. By default an object can be used as a key in a hashmap based on it's uniqueness.

There is no reason that the values have to be ArrayLists instead just use List.

HashMap&lt;Node, List&lt;Node&gt;&gt; graph = new HashMap&lt;&gt;();


This can clean up the init code as you don't have to do new ArrayList&lt;&gt; and can instead just pass Arrays.asList(nodes.get("a")) directly.

Though having said all that you can just put the List of neighbours straight in the Node and not deal with having to look it up in strongConnect.