LeetCode #2 — Medium

Add Two
Numbers

A complete visual walkthrough of linked list digit-by-digit addition — from brute force to the elegant carry-forward solution.

Solve on LeetCode
The Problem

Problem Statement

You are given two non-empty linked lists representing two non-negative integers. The digits are stored in reverse order, and each of their nodes contains a single digit. Add the two numbers and return the sum as a linked list.

You may assume the two numbers do not contain any leading zero, except the number 0 itself.

Example 1:

Input: l1 = [2,4,3], l2 = [5,6,4]
Output: [7,0,8]
Explanation: 342 + 465 = 807.

Example 2:

Input: l1 = [0], l2 = [0]
Output: [0]

Example 3:

Input: l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
Output: [8,9,9,9,0,0,0,1]

Constraints:

  • The number of nodes in each linked list is in the range [1, 100].
  • 0 <= Node.val <= 9
  • It is guaranteed that the list represents a number that does not have leading zeros.
Patterns Used

Roadmap

  1. The Brute Force Trap
  2. The Core Insight — Grade-School Addition
  3. Algorithm Walkthrough
  4. Edge Cases
  5. Full Annotated Code
  6. Interactive Demo
  7. Complexity Analysis
Step 01

The Brute Force Trap

The tempting approach: convert each linked list to an integer, add them, and convert back. Let's see it visually:

Convert, Add, Convert Back

l1
2
4
3
null
reverse digits → 342
l2
5
6
4
null
reverse digits → 465
342 + 465 = 807
↓ convert 807 back to linked list ↓
7
0
8
null
⚠️

This fails for large numbers. Linked lists can have up to 100 nodes, meaning numbers up to 10100. That overflows every integer type — even long (max ~9.2 × 1018) and BigInteger adds unnecessary complexity. We need a solution that works digit by digit.

Step 02

The Core Insight — Grade-School Addition

Remember how you added numbers by hand in school? Column by column, right to left, carrying the overflow. The linked lists are already in reverse order — the head is the ones digit. So we just walk both lists left to right, adding digits and tracking the carry!

Digit-by-Digit Addition

At each position, compute sum = digit1 + digit2 + carry. The new digit is sum % 10, and the new carry is sum / 10.

ONES PLACE
2 + 5 + 0 = 7
digit = 7, carry = 0
TENS PLACE
4 + 6 + 0 = 10
digit = 0, carry = 1
HUNDREDS
3 + 4 + 1 = 8
digit = 8, carry = 0
💡

Key idea: The reverse storage order is a gift, not an obstacle. It means the head of each list is the least significant digit — exactly where addition starts. No reversal needed!

Step 03

Algorithm Walkthrough

Let's trace through the full algorithm with l1 = [2, 4, 3] and l2 = [5, 6, 4]. We use a dummy head node so we don't need special logic for the first node.

Iteration 1 — Ones Place
l1:
2
4
3
l2:
5
6
4
sum: 2 + 5 + 0 = 7
digit: 7 % 10 = 7
carry: 7 / 10 = 0 carry = 0
result:
7
Iteration 2 — Tens Place
l1:
2
4
3
l2:
5
6
4
sum: 4 + 6 + 0 = 10
digit: 10 % 10 = 0
carry: 10 / 10 = 1 carry = 1
result:
7
0
Iteration 3 — Hundreds Place
l1:
2
4
3
l2:
5
6
4
sum: 3 + 4 + 1 = 8
digit: 8 % 10 = 8
carry: 8 / 10 = 0 carry = 0
result:
7
0
8
null
Both lists exhausted, carry = 0 → Done!

The dummy head trick: We create a placeholder node at the start so curr.next = new ListNode(digit) always works, even for the first real node. At the end, we return dummy.next to skip it.

Step 04

Edge Cases

The algorithm handles all edge cases naturally thanks to the while (l1 != null || l2 != null || carry != 0) condition:

Different Length Lists

When one list is shorter, we treat missing digits as 0.

l1
9
9
null
l2
1
null
99 + 1 = 100
0
0
1
null

Carry Produces an Extra Digit

The || carry != 0 part is crucial — it ensures we don't lose the final carry.

l1
5
null
l2
5
null
5 + 5 = 10 → carry creates a new node
0
1
null

Single Digit Lists

Works perfectly with one node each — just one iteration (plus one for carry if needed).

[3] + [4] = [7]
No carry needed
[7] + [8] = [5, 1]
Carry creates second node
🎯

No special cases needed. The loop naturally handles all scenarios: when one list runs out (treat as 0), when both run out but carry remains (one more node), and when lists are the same length. The code stays clean and branch-free.

Step 05

Full Annotated Code

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int val) { this.val = val; }
 * }
 */
class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {

        // Dummy head — avoids special-casing the first node
        ListNode dummy = new ListNode(0);
        ListNode curr = dummy;
        int carry = 0;

        // Keep going while there are digits OR a leftover carry
        while (l1 != null || l2 != null || carry != 0) {

            // Start with the carry from the previous column
            int sum = carry;

            // Add l1's digit if it exists, then advance
            if (l1 != null) {
                sum += l1.val;
                l1 = l1.next;
            }

            // Add l2's digit if it exists, then advance
            if (l2 != null) {
                sum += l2.val;
                l2 = l2.next;
            }

            // Extract carry for the next column
            carry = sum / 10;

            // Create node with the ones digit of sum
            curr.next = new ListNode(sum % 10);
            curr = curr.next;
        }

        // Skip the dummy head
        return dummy.next;
    }
}
Step 06

Interactive Demo

Enter two numbers as comma-separated digits in reverse order (ones digit first). Step through the addition to see carries and the result list being built.

⚙ Linked List Addition Visualizer



Step 07

Complexity Analysis

Time
O(max(m, n))
Space
O(max(m, n))

We traverse both linked lists in a single pass, processing one node per iteration. The loop runs for max(m, n) iterations (plus one extra if a final carry exists), and each iteration does O(1) work: one addition, one modulo, one division, and one node allocation. The result list contains at most max(m, n) + 1 nodes, so the output space is also O(max(m, n)).

Approach Breakdown

STRING CONVERSION
O(max(m, n)) time
O(max(m, n)) space

Traverses each list once to build the integer string, then performs one addition and one conversion back. Each traversal is O(m) or O(n). The intermediate integer requires O(max(m, n)) digits of storage, and it fails entirely for lists longer than ~18 nodes due to integer overflow.

DIGIT-BY-DIGIT
O(max(m, n)) time
O(max(m, n)) space

A single while loop walks both lists in lockstep. Each iteration does O(1) work: one addition, one modulo, one division, and one node allocation. The output list has at most max(m, n) + 1 nodes (the extra node covers a final carry). Works for arbitrarily long lists with no overflow risk.

🎯

This is provably optimal: We must read every digit in both inputs, so O(max(m, n)) time is a lower bound. The output itself requires O(max(m, n)) space. The carry propagation at each step is O(1) — it never cascades — so there is no hidden cost. Both approaches share the same big-O, but digit-by-digit addition handles lists of any length without integer overflow.

Coach Notes

Common Mistakes

Review these before coding to avoid predictable interview regressions.

Losing head/tail while rewiring

Wrong move: Pointer updates overwrite references before they are saved.

Usually fails on: List becomes disconnected mid-operation.

Fix: Store next pointers first and use a dummy head for safer joins.

Overflow in intermediate arithmetic

Wrong move: Temporary multiplications exceed integer bounds.

Usually fails on: Large inputs wrap around unexpectedly.

Fix: Use wider types, modular arithmetic, or rearranged operations.