GUIDE · Post-mortems

Post-mortem: make all degrees even with ≤ 2 edges

MAKE ALL DEGREES EVEN BY ADDING <= 2 EDGES (Handshake AI / CodeSignal, Oct 2026)

lesson1 sectionruns in browserHandshake AI / CodeSignal, Oct 20260 asserts108 lines

At a glance

POST-MORTEM
MAKE ALL DEGREES EVEN BY ADDING <= 2 EDGES (Handshake AI / CodeSignal, Oct 2026)
TASK
Undirected simple graph as an adjacency matrix (bool[][] or 0/1, n <= 50, symmetric, diagonal false). Can you add at most two edges (no multi-edges, no self-loops) so every vertex has even degree?
KEY
It's a PARITY problem. An edge (u, v) flips the parity of exactly u and v. Handshake lemma -> the count of odd-degree vertices is always even. Two edges flip at most 4 distinct vertices (a vertex hit twice nets to 0).
CASES
0 odd -> true. 2 odd -> direct edge if free, else via a middle vertex w adjacent to neither. 4 odd -> some perfect matching of the four using two free edges. 6+ odd -> false.
SAY
parityhandshake lemmaodd-degree verticesperfect matchingsimple graph constraintO(n^2) to read the matrix, O(n) logic

The idea#

Picture four kids on a playground, each holding some strings. A kid is "odd" when they are holding an odd number of strings. You may tie at most two new strings. A string always joins two different kids, and you may not tie a second string where one already hangs. Every new string flips exactly two kids: the two you just joined. Tie both ends of a path of two strings to the same middle kid, and that middle kid flips twice and lands back on the parity they started with.

This is a parity problem on an undirected simple graph. One new edge flips the degree parity of exactly its two endpoints. The handshake lemma says the number of odd-degree vertices is always even, so you will never see 1, 3, or 5 odds. Two edges touch at most four vertices, and a vertex you touch twice nets back to zero. Reading the adjacency matrix is O(n²). After that the decision is O(n).

The graph arrives as a matrix, boolean or 0/1, symmetric, diagonal empty, n at most 50. The question is whether you can add zero, one, or two edges, with no self-loops and no second copy of an edge, so every degree becomes even.

Why it matters#

The slow story is "try every pair of missing edges." At n = 50 that is a pile of combinations, and it hides the structure. The fast story looks only at who is odd.

Zero odds means you are done. Add nothing. Two odds means one edge between them fixes both, if that edge is free. If they are already joined, one more edge between them is illegal, so you borrow a middle vertex w that is tied to neither. The edges u–w and w–v flip u and v once each, and flip w twice, so w stays even. If no such w exists, the answer is no. Four odds means you need both edges, and they have to pair the four vertices into two couples. There are three ways to pair four people. If any of those pairings is two free edges, take it. Six or more odds cannot be fixed. Two edges do not have enough flips.

That case split is the whole problem. Everything else is "is this edge missing?" and "did I count the odds correctly?"

The matrix will not always be numbers. A cell can be true or false. Summing a row still works, because true coerces to 1. The emptiness test has to be falsy, !matrix[u][v]. Writing matrix[u][v] === 0 treats false as occupied, since false === 0 is false, and you will refuse edges that are free.

A small example#

JS
function oddVertices(matrix) {  const odd = [];  for (let i = 0; i < matrix.length; i++) {    let degree = 0;    for (const cell of matrix[i]) degree += cell ? 1 : 0;    if (degree % 2 === 1) odd.push(i);  }  return odd;} const path = [  [0, 1, 0],  [1, 0, 1],  [0, 1, 0],];const odds = oddVertices(path);

odds is [0, 2]. The ends of a two-edge path are the odd ones. They are not yet joined, so one new edge, 0–2, makes every degree even. The middle vertex was even and you never touched it.

What the code below is doing#

The function walks each row, sums the degree, and collects the odd vertices. Then it branches on how many it found.

Zero odds returns true. A single vertex with no edges. Three isolated vertices. A triangle, where everyone has degree 2. A complete graph on five vertices, where everyone has degree 4. Nothing to add.

More than four odds returns false before any edge is considered. Three disjoint edges on six vertices leave six odds. A complete bipartite graph with two groups of three, every degree 3, does the same. Two new edges cannot flip six parities.

Two odds takes the direct edge when it is free. A path of four has odd ends that are not adjacent, so one edge closes it. When that edge already exists, look for a middle w free toward both. Two vertices with only that one edge have no middle, so the answer is false. Three vertices with the odds already joined leaves the third free on both sides, and two edges through it work. A complete graph on four vertices minus the odds' edge blocks every middle. A middle that is free on only one side also fails. One last case keeps an early return honest: the first candidates are blocked, and only the final vertex is a legal middle.

Four odds tries the three perfect matchings. Two disjoint edges can be cross-matched along edges that do not exist yet. A star, one center and three leaves, uses an existing edge in every pairing, so it fails. A complete graph on four vertices has nothing free. An even bystander does not change the four odds. One case leaves only the third pairing free, so the checks are a real or across all three.

The checks at the bottom of the sheet rerun a two-odd success and a four-odd failure with numeric 0 and 1, so a solution that only understands booleans gets caught. The harness copies the matrix before each call, so a solution that writes into the grid cannot poison the next case. Each case is named after the branch it exercises. The first failure name tells you which branch is missing.

When you talk about it, say this: it is parity. An edge flips two vertices, the handshake lemma keeps the odd count even, two edges fix at most four odds, and the logic after you read the matrix is O(n).

Everything below is the demonstration. Same idea, written as code you can run.

JSpost-mortems/even-degrees-with-two-edges.js · setup
'use strict';const assert = require('node:assert/strict'); function canMakeAllDegreesEven(matrix) {  const n = matrix.length;  const odd = [];  for (let i = 0; i < n; i++) {    const degree = matrix[i].reduce((s, x) => s + x, 0);   // + coerces: true + true === 2    if (degree % 2 === 1) odd.push(i);  }   if (odd.length === 0) return true;  if (odd.length > 4) return false;                   // 2 edges can fix at most 4 parities   // May we add edge u-v?  Use falsy, not `=== 0`: the harness may hand you booleans, and  // `false === 0` is false.  const free = (u, v) => u !== v && !matrix[u][v];   if (odd.length === 2) {    const [u, v] = odd;    if (free(u, v)) return true;                      // one edge    for (let w = 0; w < n; w++) {                     // two edges through a middle vertex      if (free(u, w) && free(w, v)) return true;      // w flips twice -> stays even    }    return false;  }   const [a, b, c, d] = odd;                           // exactly 4 odd: need a perfect matching  return (free(a, b) && free(c, d))      || (free(a, c) && free(b, d))      || (free(a, d) && free(b, c));} // ─────────────────────────────────────────────────────────────────────────────// TEST HARNESS — each case is named after the branch of logic it exercises.// To debug your own version: paste it below as `mine`, uncomment the runTests(mine)// line, run `node post-mortems/even-degrees-with-two-edges.js`. The first FAIL line// tells you which branch is missing or wrong.// ─────────────────────────────────────────────────────────────────────────────const graph = (n, edges, on = true, off = false) => {  const m = Array.from({ length: n }, () => new Array(n).fill(off));  for (const [u, v] of edges) { m[u][v] = on; m[v][u] = on; }  return m;};const K = n => graph(n, Array.from({ length: n }, (_, i) => Array.from({ length: n - i - 1 }, (_, j) => [i, i + j + 1])).flat()); const CASES = [  // [ what it tests, matrix, expected ]  ['0 odd: single vertex, no edges',                       graph(1, []), true],  ['0 odd: 3 isolated vertices',                           graph(3, []), true],  ['0 odd: triangle (all degree 2)',                       graph(3, [[0, 1], [1, 2], [2, 0]]), true],  ['0 odd: K5 (all degree 4)',                             K(5), true],   ['2 odd: path of 4, ends not adjacent -> add 1 edge',    graph(4, [[0, 1], [1, 2], [2, 3]]), true],  ['2 odd: single edge on 2 vertices, no middle vertex',   graph(2, [[0, 1]]), false],  ['2 odd: edge exists, free middle vertex -> add 2 edges', graph(3, [[0, 1]]), true],  ['2 odd: edge exists, every middle vertex blocked (K4 minus 0-1)', graph(4, [[0, 2], [0, 3], [1, 2], [1, 3], [2, 3]]), false],  ['2 odd: edge exists, every middle vertex free on ONE side only', graph(4, [[0, 1], [1, 2], [2, 3], [1, 3]]), false],   // odd {0,1}; 2 and 3 both touch 1  ['2 odd: edge exists, only the LAST vertex works as middle', graph(5, [[0, 1], [0, 2], [1, 2], [0, 3], [1, 3]]), true],   ['4 odd: two disjoint edges -> cross-match them',        graph(4, [[0, 1], [2, 3]]), true],  ['4 odd: star K1,3 -> every pairing hits an existing edge', graph(4, [[0, 1], [0, 2], [0, 3]]), false],  ['4 odd: K4 -> nothing free',                            K(4), false],  ['4 odd: plus an even bystander vertex',                 graph(5, [[0, 1], [2, 3]]), true],  ['4 odd: only the third pairing (a,d)+(b,c) is free',    graph(5, [[0, 1], [0, 2], [0, 4], [3, 4]]), true],   // odd {0,1,2,3}; 0-1 and 0-2 taken, 0-3 and 1-2 free   ['6 odd: three disjoint edges',                          graph(6, [[0, 1], [2, 3], [4, 5]]), false],  ['6 odd: K3,3 (all degree 3)',                           graph(6, [[0, 3], [0, 4], [0, 5], [1, 3], [1, 4], [1, 5], [2, 3], [2, 4], [2, 5]]), false],   ['input as 0/1 numbers instead of booleans',             graph(3, [[0, 1]], 1, 0), true],  ['input as 0/1 numbers, 4 odd star',                     graph(4, [[0, 1], [0, 2], [0, 3]], 1, 0), false],]; function runTests(fn, label = fn.name || 'fn') {  let failed = 0;  for (const [name, m, expected] of CASES) {    const got = fn(m.map(row => row.slice()));         // copy so a mutating solution can't poison later cases    if (got !== expected) {      failed++;      console.log(`FAIL [${label}] ${name}\n     expected ${expected}, got ${got}\n     matrix: ${JSON.stringify(m.map(r => r.map(Number)))}`);    }  }  return failed;} assert.equal(runTests(canMakeAllDegreesEven), 0);
01

Paste your version here and uncomment the next line:#

JS
// function mine(matrix) { /* ... */ }// if (runTests(mine) === 0) console.log('mine: all cases pass');