2. Add Two Numbers

Two numbers are stored as reversed-digit linked lists. Walk both simultaneously, tracking a carry at each position and appending result nodes via a dummy head — the same pattern as grade-school long addition.

MediumLinked ListTwo PointersSimulationTypeScript

PROBLEM What we're solving

You are given two non-empty linked lists representing non-negative integers. The digits are stored in reverse order (least-significant digit first) and each node holds a single digit. Add the two numbers and return the sum as a linked list in the same reversed format.

Concrete example: l1 = [2,4,3] represents 342; l2 = [5,6,4] represents 465. Their sum is 807, so the answer is [7,0,8].

KEY IDEA Simulate grade-school addition column by column

Insight → Because the digits are already in LSB-first order, you can walk both lists left-to-right and do exactly what you learned in school: add the two digits plus the incoming carry, write down the ones digit, and pass the tens digit to the next column. A dummy head node lets you append without special-casing the first node.

RECIPE Dummy head + single while loop

  • 0 · Create a dummy head. Allocate dummy = new ListNode(0) and point curat it. This eliminates the “first node is special” edge case — you always do cur.next = ….
  • 1 · Loop while there is work. Continue while l1, l2, or carry is non-zero. The carry condition handles the final digit when one number is longer (e.g., 999 + 1 = 1000).
  • 2 · Read digits safely. Use l1?.val ?? 0 — an exhausted list contributes 0, not an error.
  • 3 · Compute sum and carry. sum = d1 + d2 + carry. Append new ListNode(sum % 10) and set carry = Math.floor(sum / 10).
  • 4 · Advance all pointers. Move cur, and advance l1/l2only if they're not null.
  • 5 · Return dummy.next. The first real result node sits right after the sentinel.
Classic confusion → Forgetting || carry !== 0 in the loop condition. If both lists are exhausted but carry is still 1 (e.g., 5 + 5 = 10), the loop exits too early and the leading 1 is never appended. Always keep carry in the while condition.

COST Complexity & alternatives

Convert to integers, add, convert back
O(n)
Works for small inputs but overflows for very long lists.
Single-pass simulation (this approach)
O(max(m,n))
One pass; O(max(m,n)+1) new nodes; never overflows.

Space: O(max(m, n) + 1) for the result list. The +1 is for a possible carry node at the end (e.g., 5 + 5 produces [0, 1]). No stack or auxiliary data structure needed.

Pattern transfer → The dummy head + carry loop is the backbone of Multiply Strings (simulate long multiplication column by column), Plus One (single-list carry propagation), and Add Binary (same idea on strings). Whenever you build a new linked list node-by-node, reach for a dummy head — it collapses first-node special cases across all such problems.

RUN IT Walk both lists with carry, build result digit by digit

step 0 / 7
STARTAdd 2→4→3 and 5→6→4 digit by digit (LSB first). Carry starts at 0. cur points to the dummy sentinel.
1// Definition for singly-linked list.
2class ListNode {
3 val: number;
4 next: ListNode | null;
5 constructor(val = 0, next: ListNode | null = null) {
6 this.val = val;
7 this.next = next;
8 }
9}
10
11function addTwoNumbers(
12 l1: ListNode | null,
13 l2: ListNode | null
14): ListNode | null {
15 const dummy = new ListNode(0); // sentinel head — avoids edge-case for first node
16 let cur = dummy;
17 let carry = 0;
18
19 while (l1 !== null || l2 !== null || carry !== 0) {
20 const d1 = l1?.val ?? 0; // treat an exhausted list as supplying 0
21 const d2 = l2?.val ?? 0;
22 const sum = d1 + d2 + carry;
23
24 cur.next = new ListNode(sum % 10); // append the ones digit
25 carry = Math.floor(sum / 10); // propagate the tens digit
26
27 cur = cur.next;
28 if (l1) l1 = l1.next;
29 if (l2) l2 = l2.next;
30 }
31
32 return dummy.next; // skip the sentinel
33}
l12ptr43l25ptr64
State
0
p1 (l1)
0
p2 (l2)
0
carry
dummy
cur
[]
result
l1 pointerl2 pointercarry / sumresult node / digitcur pointer
slowfast

TYPESCRIPT The solution, annotated

addTwoNumbers.ts
// Definition for singly-linked list.
class ListNode {
  val: number;
  next: ListNode | null;
  constructor(val = 0, next: ListNode | null = null) {
    this.val = val;
    this.next = next;
  }
}

function addTwoNumbers(
  l1: ListNode | null,
  l2: ListNode | null
): ListNode | null {
  const dummy = new ListNode(0);   // sentinel head — avoids edge-case for first node
  let cur = dummy;
  let carry = 0;

  while (l1 !== null || l2 !== null || carry !== 0) {
    const d1 = l1?.val ?? 0;      // treat an exhausted list as supplying 0
    const d2 = l2?.val ?? 0;
    const sum = d1 + d2 + carry;

    cur.next = new ListNode(sum % 10);   // append the ones digit
    carry = Math.floor(sum / 10);        // propagate the tens digit

    cur = cur.next;
    if (l1) l1 = l1.next;
    if (l2) l2 = l2.next;
  }

  return dummy.next;   // skip the sentinel
}

Reading it block by block

Lines 13–17 — dummy sentinel. Allocating dummy = new ListNode(0) and pointing cur at it means the first real node is created the same way as every subsequent one: cur.next = new ListNode(...). No branch needed for “is this the head?”
Line 20 — loop condition. Three reasons to keep looping: l1 !== null, l2 !== null, or carry !== 0. The carry clause is the one people forget — it handles the final overflow digit (e.g., 999 + 1 = 1000).
Lines 21–23 — safe digit reads. The optional-chaining + ?? 0 idiom means an exhausted pointer silently contributes zero rather than requiring an if/else. The sum is always valid regardless of whether one list is shorter.
Lines 25–26 — append and carry. sum % 10 is the ones digit; Math.floor(sum / 10) is the carry (always 0 or 1 since max sum per column is 9 + 9 + 1 = 19). Appending directly to cur.next keeps the pointer management simple.
Lines 28–30 — advance pointers. Always advance cur. Advance l1/l2 only when not null — the loop condition already handles the case where one list is exhausted.
Line 33 — return dummy.next. dummy itself was the placeholder; the first real result digit is at dummy.next.
Complexity → O(max(m, n)) time — one pass through the longer list. O(max(m, n) + 1) space for the new result list (the +1 accounts for a possible carry-out node). No extra data structures.

INTERVIEWFollow-ups they'll ask

  • “What if the digits are stored in forward order?” Reverse both lists first (or use a stack/array to collect digits), then apply the same loop. Alternatively, recurse to the end and carry backwards — same logic, O(n) stack space.
  • “Can you do it in-place without allocating new nodes?” You can reuse nodes from the longer list for result digits, but you still need a carry node if the result is longer than both inputs. Trickier and rarely worth it — mention the tradeoff.
  • “What about very large numbers — say, 10 000 digits?” This approach handles them fine because it never converts to a native integer. The integer overflow problem only arises if you try parseInt.
  • “How would you unit-test this?” Cover: equal-length lists, unequal lengths, carry at the end ([5]+[5]=[0,1]), single-node lists, and one list being all 9s.

MNEMONIC The one-liner

"Dummy head, carry in the loop condition, LSB-first means left-to-right is free."

TRIGGERS When you see ___ → reach for ___

two numbers as reversed linked listssingle while loop + carry
build a new linked list node by nodedummy head sentinel
digit-by-digit addition with overflowsum % 10 + Math.floor(sum / 10)
lists may have different lengthsoptional-chaining ?? 0 for exhausted ptr

SKELETON The reusable shape

skeleton.ts
const dummy = new ListNode(0);
let cur = dummy;
let carry = 0;

while (l1 !== null || l2 !== null || carry !== 0) {
  const d1 = l1?.val ?? 0;
  const d2 = l2?.val ?? 0;
  const sum = d1 + d2 + carry;
  cur.next = new ListNode(sum % 10);
  carry = Math.floor(sum / 10);
  cur = cur.next;
  if (l1) l1 = l1.next;
  if (l2) l2 = l2.next;
}
return dummy.next;

FLASHCARDS Tap to flip

Why use a dummy head node?
It lets you always do cur.next = new ListNode(…) — no special case for the first result node.
tap to flip
1 / 7
Answer to reveal explanations. No penalty for retries.Score: 0 / 9
QUESTION 01
Given l1 = [2,4,3] and l2 = [5,6,4], what does the function return?
QUESTION 02
Why must the while-loop condition include || carry !== 0?
QUESTION 03
What is the time complexity of this algorithm?
QUESTION 04
Why does return dummy.next rather than return dummy?
QUESTION 05
l1 = [9,9,9], l2 = [1]. What is the output?
QUESTION 06
What does l1?.val ?? 0 return when l1 is null?
QUESTION 07
Which pattern does the dummy-head technique directly transfer to?
QUESTION 08
#2 · Add Two NumbersWalk both reversed-digit linked lists simultaneously with a carry variable, building a new list via a dummy head. Append a final carry node if it survives past both lists.Which algorithmic approach does this primarily use?
QUESTION 09
#2 · Add Two NumbersWalk both reversed-digit linked lists simultaneously with a carry variable, building a new list via a dummy head. Append a final carry node if it survives past both lists.Which implementation correctly solves it?