Start learning free

LESSON 281 OF 348 · LINKED LISTS

Add Two Numbers in TypeScript: Digit-by-Digit Linked List Addition

Practice O(n) Read the full explanation

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: list1 = [2, 4, 3], list2 = [5, 6, 4]
Output: [7, 0, 8]
Explanation: 342 + 465 = 807


Example 2:

Input: list1 = [9,9,9,9,9,9,9], list2 = [9,9,9,9]
Output: [8,9,9,9,0,0,0,1]
Explanation: 9999999 + 9999 = 10009998


STEP 1 OF 12
    Ctrl + Enter to check

    TYPESCRIPT · LINKED LISTS

    Add Two Numbers in TypeScript, explained

    Problem overview

    The method addTwoNumbers receives two non-empty linked lists, list1 and list2. Each represents a non-negative integer with one digit per node, and the digits are stored in reverse order: the head holds the ones digit, the next node the tens digit, and so on. The method must return the sum in the same format, as a new linked list.

    In the first example, [2, 4, 3] is the number 342 and [5, 6, 4] is 465. Their sum, 807, is returned as [7, 0, 8]. In the second, [9, 9, 9, 9, 9, 9, 9] plus [9, 9, 9, 9] is 9999999 + 9999 = 10009998, returned as [8, 9, 9, 9, 0, 0, 0, 1]. The result is one digit longer than the longer input.

    Approach: do what you do on paper

    You add numbers on paper from the right, digit by digit, writing the ones digit and carrying the rest. The reverse order of the lists makes this convenient: the head of each list is already the rightmost column, so one pass from the heads does the whole job.

    Converting each list to a number, adding, and converting back looks tempting, but it breaks. In TypeScript, number is a 64-bit floating-point value, and integers above 2**53 lose precision. A list can hold far more digits than that allows. Adding column by column never builds a big number, so it works for any length.

    For each column, compute digitSum = digit1 + digit2 + carry. The new digit is digitSum % 10 and the new carry is 1 when digitSum is above 9, otherwise 0. The largest possible column is 9 + 9 + 1 = 19, so the carry never exceeds 1.

    Two details make the loop simple:

    • When one list is shorter, its missing digits count as 0, so each list is read only while it still has nodes.
    • The loop continues while either list has digits or a carry is left. That is what produces the extra leading 1 in 9999999 + 9999.

    Step by step

    1. Create the cursors node1 and node2 at the two heads. Moving them leaves the parameters untouched.
    2. Build a dummy head and a tail that starts on it. Every new digit is appended after tail, so the first digit needs no special case. The dummy's own value is never returned.
    3. Start carry at 0. There is nothing to carry into the first column.
    4. Loop while any of the three is truthy: node1, node2 or carry. A node is truthy and null is falsy, and carry is truthy when it is 1.
    5. Seed digitSum with carry. Declared inside the loop, it starts fresh for every column.
    6. Add the digit of node1 if it exists, then move node1 to the next node.
    7. Do the same for node2 with a separate if. An else would be wrong, because both lists can contribute to the same column.
    8. Update the carry with a ternary. The comparison digitSum > 9 is a boolean, and TypeScript won't accept a boolean in a number variable, so ? 1 : 0 turns it into a number.
    9. Reduce digitSum to one digit with %= 10.
    10. Append a new node holding the digit to tail.next, then move tail onto it so the next digit follows rather than replaces it.
    11. Return head.next, skipping the dummy.

    The solution in TypeScript

    class Solution {
        addTwoNumbers(list1: ListNode | null, list2: ListNode | null): ListNode | null {
            let node1 = list1, node2 = list2;
            let head = new ListNode(), tail = head;
            let carry = 0;
            while (node1 || node2 || carry) {
                let digitSum = carry;
                if (node1) {
                    digitSum += node1.val;
                    node1 = node1.next;
                }
                if (node2) {
                    digitSum += node2.val;
                    node2 = node2.next;
                }
                carry = digitSum > 9 ? 1 : 0;
                digitSum %= 10;
                tail.next = new ListNode(digitSum);
                tail = tail.next;
            }
            return head.next;
        }
    }
    

    The TypeScript details worth knowing:

    • Inferred types. let carry = 0 gives carry the type number, and node1 = list1 gives node1 the type of list1. No annotations are needed.
    • A boolean is not a number. Writing carry = digitSum > 9 makes the compiler report that boolean is not assignable to number. Types are erased, so the compiled JavaScript would still run, but carry would hold true instead of a number. The ternary stores a real 1 or 0.
    • Truthiness. In while (node1 || node2 || carry), objects count as true, null and 0 count as false. One condition covers all three cases.
    • Several variables in one let. In let head = new ListNode(), tail = head;, the second variable is initialised from the first. Both refer to the same node at first, and only tail moves afterwards. new ListNode() without arguments relies on the class's default value, which is 0.
    • %=. digitSum %= 10 is shorthand for digitSum = digitSum % 10. For the non-negative sums here, it leaves the ones digit.
    • Why head is never reassigned. It keeps pointing at the dummy node, which is how the final line finds the start of the real result.

    Complexity

    Let m and n be the lengths of the two lists.

    Time: O(max(m, n)). The loop runs once per column, up to the longer length plus at most one extra column for a final carry. Each iteration does a constant amount of work.

    Space: O(max(m, n)). The result list has one node per column, which is required by the output. Beyond that, the method uses a few variables.

    Edge cases and common mistakes

    • A final carry. [5] plus [5] gives [0, 1]. Leaving carry out of the loop condition would return [0] and lose the digit.
    • Different lengths. [1] plus [9, 9] gives [0, 0, 1]: the carry travels through the longer list after the shorter one ends.
    • Zero. [0] plus [0] gives [0]. The loop runs once and stops, with no extra node.
    • Using else for the second list. This skips digits of list2 whenever list1 still has digits, and the result is wrong.
    • Not moving tail. Without tail = tail.next, every digit overwrites head.next, and the answer ends up as just the last digit.
    • Returning head. That includes the dummy node, so the result starts with a stray 0.
    • Converting to numbers. Parsing the lists to numbers and adding them gives wrong answers for long lists, because of the precision limit described above.

    Testing

    • [2, 4, 3] and [5, 6, 4] give [7, 0, 8].
    • [9, 9, 9, 9, 9, 9, 9] and [9, 9, 9, 9] give [8, 9, 9, 9, 0, 0, 0, 1].
    • [0] and [0] give [0].
    • [5] and [5] give [0, 1].
    • [1] and [9, 9] give [0, 0, 1].
    Keep going with TypeScriptAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free