Last Updated: March 28, 2026
We're given an undirected graph and need to decide whether we can split all its nodes into two groups such that no edge connects two nodes within the same group. This is the classic graph bipartiteness problem, and it comes up surprisingly often in disguise (scheduling, matching, conflict resolution).
Here's the key insight: a graph is bipartite if and only if it contains no odd-length cycles. Think about it. If you try to 2-color a cycle of length 3 (a triangle), you'll always end up with two adjacent nodes sharing the same color. But a cycle of length 4 can be perfectly 2-colored by alternating colors.
So the problem reduces to: can we assign one of two colors to every node such that no two adjacent nodes share the same color? If we can, the graph is bipartite. If we ever find a conflict (a node's neighbor already has the same color), it's not.
One detail that's easy to miss: the graph may not be connected. We need to check every connected component independently. A graph is bipartite only if all of its components are bipartite.
1 <= n <= 100 -> Very small graph. Even O(n^2) or O(n^3) solutions would be fast. But the standard BFS/DFS approach is already O(V + E), which is optimal.0 <= graph[u].length < n -> Each node can have up to n-1 neighbors, so the graph can be dense. In the worst case, E = O(n^2) = O(10,000). Still tiny.The most natural way to check bipartiteness is to try 2-coloring the graph using BFS. Pick any uncolored node, assign it color 0, and put it in a queue. Then process the queue: for each node, look at all its neighbors. If a neighbor hasn't been colored yet, assign it the opposite color and add it to the queue. If a neighbor is already colored with the same color as the current node, we've found a conflict, and the graph isn't bipartite.
Why BFS specifically? BFS explores nodes level by level. In a bipartite graph, all nodes at even levels get one color, and all nodes at odd levels get the other. This layer-by-layer exploration maps perfectly to the alternating color assignment we need.
Since the graph might be disconnected, we loop through all nodes and kick off a BFS from any node that hasn't been colored yet. If every component passes the 2-coloring check, the graph is bipartite.
color array of size n, initialized to -1 (uncolored).0 to n-1:0 and add it to a BFS queue.u.v of u:v is uncolored, assign it 1 - color[u] (the opposite color) and enqueue it.v already has the same color as u, return false.true.BFS is already optimal at O(V + E), but DFS offers a recursive alternative that some find more elegant. Let's see how the same coloring logic works with depth-first traversal.
The DFS approach uses the exact same coloring logic, just with a different traversal strategy. Instead of exploring level by level, we dive deep along one path, coloring as we go, and backtrack when we hit a dead end or a conflict.
The recursive structure makes the code quite clean: color the current node, then recursively try to color all uncolored neighbors with the opposite color. If any recursive call finds a conflict, propagate the failure upward.
Some people find DFS easier to reason about because the recursion naturally handles the "try one path, then backtrack" pattern. Others prefer BFS because the iterative queue is more explicit. Both are equally valid in an interview.
DFS and BFS both work here because the core logic is identical: assign alternating colors and check for conflicts. The traversal order doesn't matter for correctness. What matters is that every node gets colored, and every edge gets checked.
The recursive DFS has one subtle advantage: it's often shorter and easier to write quickly in an interview. The downside is that for very deep graphs, recursion can blow the call stack. With n at most 100 in this problem, that's not a concern.
color array of size n, initialized to -1 (uncolored).0 to n-1:0.false, return false.false.true.true.Both BFS and DFS are optimal at O(V + E). Union Find offers a completely different perspective: instead of coloring nodes, we group neighbors together and check for contradictions.
Here's a completely different way to think about bipartiteness. Instead of coloring, think about grouping. For a bipartite graph, every node's neighbors must all belong to the opposite partition. That means all neighbors of a node should be in the same set as each other (just not in the same set as the node itself).
Union Find gives us exactly the machinery to track these groupings. For each node u, we union all of u's neighbors together (they must all be in the same partition). Then we check: is u in the same set as any of its neighbors? If yes, we have a contradiction, and the graph isn't bipartite.
This approach is a bit less intuitive than graph coloring, but it's a good demonstration of Union Find skills, which interviewers love to see.
The key insight is that in a bipartite graph, all neighbors of a node belong to the opposite partition. If all of node u's neighbors are in the opposite partition, they must all be in the same partition as each other. Union Find tracks exactly this: we union all neighbors together and then verify that the node itself isn't in that same set.
If find(u) == find(neighbor), it means some chain of edges forced u and its neighbor into the same group, which means there's an odd cycle somewhere, and the graph can't be bipartite.
n nodes.u from 0 to n-1:u has no neighbors, skip it.u together (they must be in the same partition).u and its first neighbor are in the same set. If yes, return false.true.