43. Multiply Strings

Multiply two non-negative integers represented as strings without converting them to BigInt or native numbers. The key insight is that digit i of num1 times digit j of num2 always lands in exactly one slot of the result — no guessing needed.

MediumSchoolbook MultiplicationArray / String MathTypeScript

PROBLEM What we're solving

Given two non-negative integers as strings num1 and num2, return their product as a string — no built-in big-integer conversions allowed. Example: num1="23", num2="45""1035". Another: num1="999", num2="999""998001".

KEY IDEA Every digit pair has a fixed destination slot

Insight → when you multiply digit at index i of num1 by digit at index j of num2, the product's units go to result slot i+j+1 and the tens (carry) go to slot i+j — always, regardless of value. Allocate a result[m+n] array, accumulate every product into its two slots, and resolve carries in one final left-to-right pass that is already done inside the inner loop.

RECIPE Allocate → multiply into slots → strip zeros

  • 0 · Allocate. Create res = new Array(m+n).fill(0). A product of an m-digit and n-digit number has at most m+n digits.
  • 1 · Outer loop over num1 (right-to-left). For each index i, grab the digit value d1 = num1.charCodeAt(i) - 48.
  • 2 · Inner loop over num2 (right-to-left). For each index j, compute mul = d1 * (num2.charCodeAt(j) - 48).
  • 3 · Place and carry in-place. Add mul to res[i+j+1], then propagate the carry: res[i+j] += Math.floor(sum / 10). Because we loop from right-to-left, by the time we touch slot i+j again its carry has already landed.
  • 4 · Stringify and strip. res.join('').replace(/^0+/, ''). Guard for all-zero by returning "0" when the result is empty.
Classic confusion → people mix up i+j (carry slot) and i+j+1 (units slot). Think of it this way: if i=0, j=0 and both digits are 9, the product 81 writes 1 to slot 1 (units) and adds 8 to slot 0 (carry). Slot i+j+1 is always the units slot.

COST Complexity & alternatives

Parse to BigInt / number
Overflow!
Native numbers cap at 2⁵³; BigInt is usually disallowed in this problem.
Schoolbook array
O(m·n)
O(m+n) space for the result; O(m·n) multiply steps.

Space note

The result array is always exactly m+n slots — O(m+n) space. No extra carry buffer is needed because we accumulate carries in-place. The FFT-based Karatsuba algorithm can reach O(n log n) but is never expected in interviews.

Pattern transfer → the "digit-position destination" trick powers Add Binary, Add Strings, Plus One, and String to Integer (atoi). Any time arithmetic is done character-by-character, think about where each position's contribution lands in the output.

RUN IT Digit i × digit j lands at slots i+j and i+j+1

step 0 / 11
STARTMultiply 23 × 45. Allocate res[4] filled with zeros (m+n = 2+2).
1function multiply(num1: string, num2: string): string {
2 const m = num1.length, n = num2.length;
3 const res = new Array<number>(m + n).fill(0);
4
5 // schoolbook: digit i * digit j lands at positions i+j and i+j+1
6 for (let i = m - 1; i >= 0; i--) {
7 for (let j = n - 1; j >= 0; j--) {
8 const mul = (num1.charCodeAt(i) - 48) * (num2.charCodeAt(j) - 48);
9 const p1 = i + j; // carry position
10 const p2 = i + j + 1; // units position
11 const sum = mul + res[p2];
12 res[p2] = sum % 10;
13 res[p1] += Math.floor(sum / 10);
14 }
15 }
16
17 // strip leading zeros, then join
18 const result = res.join('').replace(/^0+/, '');
19 return result === '' ? '0' : result;
20}
0
1
2
3
res
0
0
0
0
State
i:
j:
mul:
p1 (carry):
p2 (units):
sum:
carry:
units slot (i+j+1)carry slot (i+j)value writtenleading zero stripped
slowfast

TYPESCRIPT The solution, annotated

multiplyStrings.ts
function multiply(num1: string, num2: string): string {
  const m = num1.length, n = num2.length;
  const res = new Array<number>(m + n).fill(0);

  // schoolbook: digit i * digit j lands at positions i+j and i+j+1
  for (let i = m - 1; i >= 0; i--) {
    for (let j = n - 1; j >= 0; j--) {
      const mul = (num1.charCodeAt(i) - 48) * (num2.charCodeAt(j) - 48);
      const p1 = i + j;       // carry position
      const p2 = i + j + 1;   // units position
      const sum = mul + res[p2];
      res[p2] = sum % 10;
      res[p1] += Math.floor(sum / 10);
    }
  }

  // strip leading zeros, then join
  const result = res.join('').replace(/^0+/, '');
  return result === '' ? '0' : result;
}

Reading it block by block

Line 3 — allocate the result buffer. A product of an m-digit and n-digit number has at most m+n digits. Filling with zeros means every slot starts at 0 — no undefined checks needed.
Lines 6–7 — outer loop, right-to-left. We iterate from the least-significant digit of num1 so that carry propagation flows naturally toward the front of the array. Using charCodeAt(i) - 48 converts '0'–'9' to 0–9 without parseInt.
Lines 8–15 — inner loop, place product + carry. For each pair (i, j), mul is the raw product (0–81). We add it to res[p2] (the units slot), take modulo 10 to keep just the digit, and propagate the tens to res[p1]. This works without a separate carry-resolve pass because we always move the carry left (toward smaller indices), and we process right-to-left.
Lines 19–20 — stringify and strip. join('') converts the digit array to a string. The regex strips leading zeros. If the result is the empty string (input was '0'), we return '0'.
Complexity → O(m·n) time — every pair of digits is visited once. O(m+n) space for the result array. In practice m+n ≤ 400 per the problem constraints, so this is very fast.

INTERVIEWFollow-ups they'll ask

  • "Can you add two big-integer strings?" Same idea — one loop from right-to-left, track a carry, build the result in reverse and then reverse(). See Add Strings.
  • "What if one input is '0'?" The loop still runs but every product is 0, so the result array stays all-zero. The regex strip returns empty string, and the guard returns "0".
  • "Can you do it in less space?" No — the output itself is m+n chars, so O(m+n) is the minimum.
  • "Why right-to-left?" Digit at position i from the right has place value 10^i. Processing right-to-left means the carry always goes to a slot we'll visit again (or have already accumulated into), keeping the in-place propagation correct.
  • "Negative inputs?"Strip a leading '-', multiply, then prepend '-' if exactly one input was negative.

OPTIMAL Schoolbook Multiplication

multiplyStrings.ts
function multiply(num1: string, num2: string): string {
  const m = num1.length, n = num2.length;
  const res = new Array<number>(m + n).fill(0);

  // schoolbook: digit i * digit j lands at positions i+j and i+j+1
  for (let i = m - 1; i >= 0; i--) {
    for (let j = n - 1; j >= 0; j--) {
      const mul = (num1.charCodeAt(i) - 48) * (num2.charCodeAt(j) - 48);
      const p1 = i + j;       // carry position
      const p2 = i + j + 1;   // units position
      const sum = mul + res[p2];
      res[p2] = sum % 10;
      res[p1] += Math.floor(sum / 10);
    }
  }

  // strip leading zeros, then join
  const result = res.join('').replace(/^0+/, '');
  return result === '' ? '0' : result;
}
Complexity → O(m·n) time — every pair of digits is visited once. O(m+n) space for the result array. In practice m+n ≤ 400 per the problem constraints, so this is very fast.

ALT 1 Brute force — partial products plus repeated string addition

O(m·n + n·(m+n)) time · O(m+n) space

Spell out grade-school long multiplication literally: for each digit of num2 build the full partial product (digit × num1, shifted by its place), then add all the partial products together with a string adder. No clever index math — just the steps you'd write on paper.

approach-2.ts
function multiply(num1: string, num2: string): string {
  if (num1 === '0' || num2 === '0') return '0';

  // Add two non-negative integer strings, digit by digit.
  function addStrings(a: string, b: string): string {
    let i = a.length - 1, j = b.length - 1, carry = 0;
    let out = '';
    while (i >= 0 || j >= 0 || carry > 0) {
      const da = i >= 0 ? a.charCodeAt(i--) - 48 : 0;
      const db = j >= 0 ? b.charCodeAt(j--) - 48 : 0;
      const sum = da + db + carry;
      out = String(sum % 10) + out;
      carry = Math.floor(sum / 10);
    }
    return out;
  }

  let result = '0';
  // num2's last digit has place 0, so it gets that many trailing zeros.
  for (let j = num2.length - 1; j >= 0; j--) {
    const d = num2.charCodeAt(j) - 48;
    const place = num2.length - 1 - j;

    // Build partial = d * num1, single-digit times the whole number.
    let carry = 0;
    let partial = '';
    for (let i = num1.length - 1; i >= 0; i--) {
      const prod = d * (num1.charCodeAt(i) - 48) + carry;
      partial = String(prod % 10) + partial;
      carry = Math.floor(prod / 10);
    }
    if (carry > 0) partial = String(carry) + partial;

    partial += '0'.repeat(place);            // shift by its decimal place
    result = addStrings(result, partial);    // accumulate
  }

  return result;
}
Note → Correct, but it materialises a full (m+n)-length string per digit of num2 and re-runs a string adder n times, so it does noticeably more allocation and work than the single in-place result array. The schoolbook method folds every digit-pair directly into one buffer in O(m·n), avoiding the repeated additions entirely.

MNEMONIC The one-liner

"Digit i times digit j — the units go to slot i+j+1, carry goes left to i+j."

TRIGGERS When you see ___ → reach for ___

"multiply/add large integers as strings"result[m+n] array, digit-pair loops
arithmetic on digits without overflowcharCodeAt(i) - 48, index math
"no BigInt / parseInt allowed"schoolbook array multiply
result at most m+n digits wideallocate m+n zeros, strip leading zeros

SKELETON The reusable shape

skeleton.ts
function multiply(num1: string, num2: string): string {
  const m = num1.length, n = num2.length;
  const res = new Array<number>(m + n).fill(0);

  for (let i = m - 1; i >= 0; i--) {
    for (let j = n - 1; j >= 0; j--) {
      const mul = (num1.charCodeAt(i) - 48) * (num2.charCodeAt(j) - 48);
      const sum = mul + res[i + j + 1];
      res[i + j + 1] = sum % 10;
      res[i + j] += Math.floor(sum / 10);
    }
  }

  const result = res.join('').replace(/^0+/, '');
  return result === '' ? '0' : result;
}

FLASHCARDS Tap to flip

What size should the result array be?
m + n — the product of an m-digit and n-digit number has at most m+n digits.
tap to flip
1 / 7
Answer to reveal explanations. No penalty for retries.Score: 0 / 9
QUESTION 01
For num1="23" (m=2) and num2="45" (n=2), what size is the result array?
QUESTION 02
When processing digit i=1 of num1 and digit j=0 of num2, where does the units digit of their product land?
QUESTION 03
What is the time complexity of the schoolbook array approach?
QUESTION 04
Trace "2" × "3": after the double loop what does res contain?
QUESTION 05
Why does the code return "0" when the stripped string is empty?
QUESTION 06
What happens if you swap i+j and i+j+1 (put the product in res[i+j] instead of res[i+j+1])?
QUESTION 07
Which technique converts the digit character '7' to the integer 7 most efficiently?
QUESTION 08
#43 · Multiply StringsSchoolbook multiplication into a result array of length m+n: digits num1[i] and num2[j] contribute to positions i+j and i+j+1. Resolve carries from right to left and strip leading zeros.Which algorithmic approach does this primarily use?
QUESTION 09
#43 · Multiply StringsSchoolbook multiplication into a result array of length m+n: digits num1[i] and num2[j] contribute to positions i+j and i+j+1. Resolve carries from right to left and strip leading zeros.Which implementation correctly solves it?