Multiply Two Integers Without Multiplication: Russian Peasant, Bit Shifts

Multiply Two Integers Without Using Multiplication: Russian Peasant, Bit Shifts, Recursion

“Multiply two integers without using the * operator” is a classic interview question that tests your fluency with bit manipulation, recursion, and the underlying arithmetic. The naive approach (repeatedly add) gives O(n) where n is one operand. The elegant approach (Russian peasant / shift-and-add) gives O(log n). This guide covers all standard approaches with working code, the bit-trick rationale, and the variations that come up.

Problem Statement

Given two integers a and b, return a * b without using the multiplication operator. Handle:

  • Positive, negative, and zero values
  • Both operands of equal magnitude or wildly different
  • Edge cases: 0 × anything, INT_MIN, large numbers

Examples:

  • multiply(3, 5) → 15
  • multiply(-3, 5) → -15
  • multiply(0, 100) → 0
  • multiply(2, 1000000) → 2000000

Approach 1: Repeated Addition (Naive)

Add a to itself b times. Trivial; O(b) time.

def multiply_naive(a: int, b: int) -> int:
    """O(|b|) time."""
    if b < 0:
        return -multiply_naive(a, -b)
    result = 0
    for _ in range(b):
        result += a
    return result

Acceptable as a warm-up for very small operands. For b = 10^6, this is a million additions — too slow. Interviewers expect you to optimize.

Approach 2: Russian Peasant Multiplication (Standard Answer)

Also called “shift-and-add” or “binary multiplication.” Process the binary representation of b: for each bit, double a and add to result if the bit is set.

def multiply_russian(a: int, b: int) -> int:
    """O(log |b|) time using shift-and-add."""
    # Handle signs
    sign = 1
    if a < 0:
        a = -a
        sign = -sign
    if b < 0:
        b = -b
        sign = -sign

    result = 0
    while b > 0:
        if b & 1:        # if lowest bit of b is set
            result += a
        a <<= 1          # double a
        b >>= 1          # halve b (integer division)
    return sign * result


# Tests
print(multiply_russian(3, 5))    # 15
print(multiply_russian(-3, 5))   # -15
print(multiply_russian(0, 100))  # 0
print(multiply_russian(2, 1000000))  # 2000000

Complexity: O(log |b|) time, O(1) space.

Why Russian peasant works

The algorithm follows from a * b = a * (b₀ + 2b₁ + 4b₂ + ...) where bᵢ is the ith bit of b’s binary representation. Distribute: a*b₀ + 2a*b₁ + 4a*b₂ + .... The doubling of a at each step matches the powers of 2; the bit check decides whether to add.

For multiplying 13 × 7:

  • b = 7 = 111₂. Bits set at positions 0, 1, 2.
  • Step 0: bit 0 set → add a (13). a doubles to 26.
  • Step 1: bit 1 set → add a (26 → result becomes 39). a doubles to 52.
  • Step 2: bit 2 set → add a (52 → result becomes 91). a doubles to 104.
  • b is now 0 → done.

Result: 91. Verify: 13 × 7 = 91. ✓

Approach 3: Recursive Doubling

Same logic, recursive form. Useful when explaining the algorithm visually.

def multiply_recursive(a: int, b: int) -> int:
    """O(log |b|) recursion stack."""
    if b == 0:
        return 0
    if b < 0:
        return -multiply_recursive(a, -b)

    half = multiply_recursive(a, b // 2)
    if b % 2 == 0:
        return half + half
    return half + half + a

O(log n) time and recursion stack space. Some find this version more intuitive; the iterative version is slightly more efficient.

Approach 4: Built-In (For Comparison)

Python’s native * operator uses CPU multiplication for small ints and arbitrary-precision algorithms (Karatsuba, Toom-Cook) for larger ints. For interview purposes, mention this exists but implement one of the manual approaches.

Common Variations

Multiply by a constant in O(1)

Multiplying by a power of 2: just left-shift. Multiplying by 3: (x << 1) + x. Multiplying by 7: (x << 3) - x. These are useful tricks for embedded systems and compiler optimization.

Multiply two large numbers

For numbers larger than fit in machine words, use Karatsuba’s algorithm: O(n^log₂ 3) ≈ O(n^1.585). For very large (cryptographic-scale): Toom-Cook, FFT-based multiplication (Schönhage-Strassen) at O(n log n log log n). Outside scope of standard interviews.

Multiply matrices

(LeetCode #311) Different problem; same fundamental “shift and add” idea applies to matrix exponentiation for fast Fibonacci, etc.

Implement division using only addition / subtraction

Mirror image of multiplication. Use repeated subtraction or shift-and-subtract for O(log n) division.

Common Mistakes

  • Not handling negative numbers. Always normalize signs first; handle the result sign separately.
  • Integer overflow. In C/C++/Java, multiplying near INT_MAX can overflow. Use long long or BigInteger. In Python, no overflow concerns for arbitrary precision.
  • Using * in the implementation. The point of the question is to avoid the multiplication operator. Doubling via a + a or a << 1 is fine; a * 2 is cheating.
  • Off-by-one in the bit loop. The loop should continue while b > 0, not while b ≥ 0 (which is infinite).
  • Forgetting to halve b in the loop. Without b >>= 1, the loop is infinite. The bit-check pattern requires consuming bits one at a time.

Frequently Asked Questions

What’s the expected interview answer?

Russian peasant / shift-and-add multiplication. O(log n) time, O(1) space. Walk through the binary-representation reasoning. Mention the recursive variant if asked. Strong candidates handle signs cleanly and discuss why bit operations are O(1) on machine integers.

Why is this called “Russian peasant” multiplication?

Historical name. The algorithm was widely used in Russia for hand calculation before modern multiplication tables were standardized. Same algorithm is also called “Egyptian multiplication” because Egyptian scribes used a similar approach 4000 years ago.

What’s the relationship to fast exponentiation?

Both use the binary-representation trick. Fast exponentiation: square-and-multiply for x^n. Russian peasant: double-and-add for a × b. Same logarithmic structure; different operations. Both reduce a “linear in operand” problem to O(log n).

How does this scale to very large numbers?

For n-digit numbers, Russian peasant takes O(n²) total time (O(n) iterations, each O(n) for the addition). Karatsuba reduces to O(n^1.585); FFT-based to O(n log n log log n). Cryptographic libraries use these for arbitrary-precision multiplication. Python’s int handles all of this transparently.

Can I implement this in O(1) on hardware?

Yes — modern CPUs have hardware multiplication units that compute a × b in one or a few cycles for word-sized operands. The “without using *” constraint is a teaching constraint, not a real-world one. The point of the interview question is the binary-representation insight, which has broader applications (e.g., to fast exponentiation, polynomial multiplication, modular arithmetic).

See also: Fast ExponentiationPower of Two CheckTwo Sum: Find a Pair in an Array

💡Strategies for Solving This Problem

Without Using Multiplication Operator

This showed up at Amazon. The twist: "Don't use *, /, or library functions." It's testing if you understand what multiplication really is.

Approach 1: Repeated Addition

3 × 4 = 3 + 3 + 3 + 3 (add 3 four times)

O(b) time where b is the second number. Works but slow for large numbers.

Approach 2: Bit Manipulation (Russian Peasant)

The clever approach using bit shifts:

  • a × b = a × (b/2 + b/2) if b is even
  • a × b = a + a × (b-1) if b is odd

This is O(log b) time - way better.

Why Bit Manipulation

Think of b in binary: 5 = 101₂ = 4 + 1

So a × 5 = a × 4 + a × 1 = (a << 2) + a

We can decompose any multiplication into additions of bit-shifted values.

Edge Cases

  • Negative numbers (handle sign separately)
  • Zero (return 0 early)
  • Overflow (depends on language)

My interviewer specifically asked about negatives. I forgot to handle them initially - don't make that mistake.

Scroll to Top