title: Multiply Strings
slug: multiply-strings
difficulty: medium
leetcode_id: 43
leetcode_url: https://leetcode.com/problems/multiply-strings/
categories:
  - strings
  - math
patterns:
  - slug: two-pointers
    is_optimal: true

function_signature: "def multiply(num1: str, num2: str) -> str:"

test_cases:
  visible:
    - input: { num1: "2", num2: "3" }
      expected: "6"
    - input: { num1: "123", num2: "456" }
      expected: "56088"
  hidden:
    - input: { num1: "0", num2: "0" }
      expected: "0"
    - input: { num1: "999", num2: "0" }
      expected: "0"
    - input: { num1: "1", num2: "1" }
      expected: "1"
    - input: { num1: "99", num2: "99" }
      expected: "9801"
    - input: { num1: "12345", num2: "6789" }
      expected: "83810205"

description: |
  Given two non-negative integers `num1` and `num2` represented as strings, return the product of `num1` and `num2`, also represented as a string.

  **Note:** You must not use any built-in BigInteger library or convert the inputs to integer directly.

constraints: |
  - `1 <= num1.length, num2.length <= 200`
  - `num1` and `num2` consist of digits only
  - Both `num1` and `num2` do not contain any leading zero, except the number `0` itself

examples:
  - input: 'num1 = "2", num2 = "3"'
    output: '"6"'
    explanation: "2 × 3 = 6"
  - input: 'num1 = "123", num2 = "456"'
    output: '"56088"'
    explanation: "123 × 456 = 56088"

explanation:
  intuition: |
    Think back to how you learned multiplication in primary school — the "long multiplication" method. When you multiply two multi-digit numbers by hand, you don't compute the entire product at once. Instead, you multiply each digit of one number by each digit of the other, keeping track of where each partial product should go based on its position (tens, hundreds, thousands, etc.).

    The key insight is that when you multiply the digit at position `i` of one number with the digit at position `j` of another, the result contributes to positions `i + j` and `i + j + 1` in the final answer. This is because:
    - A digit at position `i` (from the right, 0-indexed) represents a value of `digit × 10^i`
    - Multiplying two such digits gives a result that spans two positions in the output

    For example, multiplying `123 × 456`:
    - `3 × 6 = 18` → contributes to positions 0 and 1 (ones and tens)
    - `2 × 6 = 12` → contributes to positions 1 and 2 (tens and hundreds)
    - And so on...

    By accumulating all these partial products into an array and handling carries, we simulate exactly what happens on paper.

  approach: |
    We simulate grade-school multiplication using an array to accumulate partial products:

    **Step 1: Handle the zero case**

    - If either `num1` or `num2` is `"0"`, return `"0"` immediately
    - This avoids unnecessary computation and handles the edge case cleanly

    &nbsp;

    **Step 2: Initialise the result array**

    - Create an array of size `len(num1) + len(num2)` filled with zeros
    - The maximum length of the product of two numbers is the sum of their lengths (e.g., `99 × 99 = 9801` has 4 digits, and `len("99") + len("99") = 4`)

    &nbsp;

    **Step 3: Multiply digit by digit**

    - Iterate through `num1` from right to left (index `i`)
    - For each digit in `num1`, iterate through `num2` from right to left (index `j`)
    - Convert characters to integers: `digit1 = int(num1[i])`, `digit2 = int(num2[j])`
    - Compute the product: `product = digit1 × digit2`

    &nbsp;

    **Step 4: Accumulate into the correct positions**

    - The positions in the result array for this product are `i + j` and `i + j + 1`
    - Add the product to what's already at position `i + j + 1` (the lower position)
    - Handle the carry: `result[i + j + 1] = sum % 10`, and add `sum // 10` to `result[i + j]`

    &nbsp;

    **Step 5: Convert array to string**

    - Join the digits in the result array
    - Strip any leading zeros (but keep at least one digit if result is zero)

  common_pitfalls:
    - title: Using Built-in Conversion
      description: |
        The problem explicitly forbids converting strings to integers directly using `int()` or similar. Solutions like `str(int(num1) * int(num2))` violate the constraints.

        The constraint exists because the input numbers can have up to 200 digits — far exceeding the range of standard 64-bit integers (`2^63 - 1` has only 19 digits). This problem tests whether you can implement arbitrary-precision arithmetic.
      wrong_approach: "int(num1) * int(num2)"
      correct_approach: "Simulate digit-by-digit multiplication"

    - title: Off-by-One Position Errors
      description: |
        When multiplying digit at index `i` with digit at index `j`, remember that indices are from the left but place values are from the right.

        If you iterate left-to-right but use `i + j` directly, you'll place digits in the wrong positions. Either reverse the strings first, or use `(len1 - 1 - i) + (len2 - 1 - j)` to convert to place value positions.

        The cleanest approach: iterate from right to left, then `i + j + 1` and `i + j` give the correct output positions.
      wrong_approach: "Using indices directly without accounting for place value"
      correct_approach: "Iterate right-to-left or adjust indices for place value"

    - title: Forgetting to Handle Carries Properly
      description: |
        Each digit position can accumulate more than 9 during the multiplication phase. You must propagate carries correctly.

        A common mistake is handling the carry only at the end, or forgetting that intermediate sums can exceed 9. Process the carry as you go by using `sum % 10` for the current position and `sum // 10` for the next position.
      wrong_approach: "Ignoring carries until the final conversion"
      correct_approach: "Handle carry during each digit multiplication"

    - title: Leading Zeros in Result
      description: |
        The result array may have leading zeros (e.g., `099` for `9 × 11 = 99`). You must strip these before returning.

        However, be careful not to strip all zeros if the result is `0` — ensure at least one zero remains for the `"0"` case.
      wrong_approach: "Returning result with leading zeros"
      correct_approach: "Strip leading zeros but preserve '0' for zero result"

  key_takeaways:
    - "**Simulate manual algorithms**: When built-ins are forbidden, implement the algorithm you'd use by hand — here, grade-school long multiplication"
    - "**Position mapping**: The product of digits at positions `i` and `j` contributes to positions `i + j` and `i + j + 1` — this is the core insight"
    - "**Arbitrary precision**: This technique handles numbers far larger than native integer types can store"
    - "**Foundation for big integer libraries**: This is essentially how BigInteger multiplication works under the hood (with optimisations like Karatsuba for very large numbers)"

  time_complexity: "O(m × n). We multiply each digit of `num1` (length m) with each digit of `num2` (length n)."
  space_complexity: "O(m + n). We use an array of size `m + n` to store the result digits."

solutions:
  - approach_name: Grade-School Multiplication
    is_optimal: true
    code: |
      def multiply(num1: str, num2: str) -> str:
          # Handle multiplication by zero
          if num1 == "0" or num2 == "0":
              return "0"

          m, n = len(num1), len(num2)
          # Result can have at most m + n digits
          result = [0] * (m + n)

          # Multiply each digit, right to left
          for i in range(m - 1, -1, -1):
              for j in range(n - 1, -1, -1):
                  # Get the actual digit values
                  digit1 = ord(num1[i]) - ord('0')
                  digit2 = ord(num2[j]) - ord('0')

                  # Multiply and add to existing value at this position
                  product = digit1 * digit2
                  # Positions in result array
                  p1, p2 = i + j, i + j + 1

                  # Add product to the lower position
                  total = product + result[p2]

                  # Store digit and carry
                  result[p2] = total % 10
                  result[p1] += total // 10

          # Convert to string, skipping leading zeros
          result_str = ''.join(map(str, result))
          return result_str.lstrip('0') or '0'
    explanation: |
      **Time Complexity:** O(m × n) — We perform m × n single-digit multiplications.

      **Space Complexity:** O(m + n) — The result array stores the product digits.

      This approach simulates the long multiplication method taught in schools. We multiply each digit pair and accumulate results at the appropriate positions, handling carries as we go. The key insight is that digits at indices `i` and `j` contribute to positions `i + j` and `i + j + 1` in the result.

  - approach_name: With Separate Carry Pass
    is_optimal: false
    code: |
      def multiply(num1: str, num2: str) -> str:
          if num1 == "0" or num2 == "0":
              return "0"

          m, n = len(num1), len(num2)
          # Accumulate products without handling carries yet
          result = [0] * (m + n)

          # Reverse for easier position calculation
          num1 = num1[::-1]
          num2 = num2[::-1]

          # Multiply all digit pairs
          for i in range(m):
              for j in range(n):
                  digit1 = ord(num1[i]) - ord('0')
                  digit2 = ord(num2[j]) - ord('0')
                  # Position i + j gets this product
                  result[i + j] += digit1 * digit2

          # Now handle all carries in a separate pass
          carry = 0
          for i in range(len(result)):
              total = result[i] + carry
              result[i] = total % 10
              carry = total // 10

          # Remove trailing zeros (they're leading after we reverse)
          while len(result) > 1 and result[-1] == 0:
              result.pop()

          # Reverse back and convert to string
          return ''.join(map(str, result[::-1]))
    explanation: |
      **Time Complexity:** O(m × n) — Same asymptotic complexity as the optimal approach.

      **Space Complexity:** O(m + n) — Same space usage.

      This alternative reverses the strings first for simpler position indexing, accumulates all products without immediate carry handling, then propagates carries in a separate pass. While equally efficient, it's slightly less elegant than handling carries inline. Included to show a valid alternative approach.
