A cycle is a path that returns to the same vertex. Sometimes a cycle is normal (a road network), sometimes it is a bug: circular package dependencies, processes blocking each other (a deadlock), a task order, a linked list closed into a ring. How to find it depends on the kind of graph.

A back edge in an undirected graph, a diamond and a ring in a directed one
A back edge in an undirected graph, a diamond and a ring in a directed one

Undirected graph

With DFS: an edge to an already visited vertex that is not the parent means a cycle. The parent is excluded because an undirected edge goes both ways. (See DFS.)

python
def has_cycle(graph):
    seen = set()

    def dfs(node, parent):
        seen.add(node)
        for nxt in graph[node]:
            if nxt == parent:
                continue                 # the edge we came by is not a cycle
            if nxt in seen or dfs(nxt, node):
                return True
        return False

    return any(node not in seen and dfs(node, None) for node in graph)

tree = {1: [2, 3], 2: [1], 3: [1, 4], 4: [3]}
loop = {1: [2, 3], 2: [1, 3], 3: [1, 2, 4], 4: [3]}
print("tree:", has_cycle(tree))
print("loop:", has_cycle(loop))
text
tree: False
loop: True

With Union-Find: add the edges one by one; if both ends are already in one set, this edge closes a cycle. Handy when edges arrive as a stream, and the basis of Kruskal. See Union-Find.