Python Exponentiation Tutorial

Final Exam & Certification

Complete this tutorial and pass the 10-question final exam to earn a downloadable certificate of completion.

skip to exam

Python gives you three ways to raise a number to a power: the ** operator, the built-in pow() function, and math.pow() from the standard library. This tutorial walks through all three, covers the edge cases that trip up beginners, and gives you hands-on exercises to lock in each concept.

What's in this Python Tutorial

Exponentiation means raising a base number to a power. When you write 2 raised to the power of 3, Python multiplies 2 by itself three times to get 8. The ** operator is the most direct way to express this in code, and it works on integers, floats, and complex numbers alike.

The ** Operator

Place the base on the left and the exponent on the right. Python evaluates the expression and returns the result. Two mathematical identities always hold: anything raised to the power of zero is 1, and anything raised to the power of one is itself.

python
result = 2 ** 3
print(result)        # 8

10 ** 0              # 1   (any number to the power of 0 is 1)
5  ** 1              # 5   (any number to the power of 1 is itself)
3  ** 4              # 81
2  ** 10             # 1024
7  ** 2              # 49

Python integers have arbitrary precision, so integer exponentiation never overflows. 2 ** 100 returns the exact 31-digit integer. The **= augmented assignment operator raises a variable to a power and reassigns the result in one step.

python
value = 3
value **= 4
print(value)         # 81  (same as value = value ** 4)

side = 5
side **= 2
print(side)          # 25  (area of a square with side 5)
Note

The ** operator is right-associative: 2 ** 3 ** 2 evaluates as 2 ** (3 ** 2) = 512, not (2 ** 3) ** 2 = 64. When chaining powers, use parentheses to make your intent explicit.

Pro Tip

125 ** (1/3) returns 4.999... rather than exactly 5.0 due to floating-point representation. Use round() or math.isclose() when comparing fractional-exponent results against expected integers.

python
import math

# ** preserves int when both operands are int
type(2 ** 3)           # <class 'int'>
type(2.0 ** 3)         # <class 'float'>

# built-in pow() matches ** for two arguments
pow(2, 8)              # 256  (int)

# pow() three-argument form: modular exponentiation
pow(2, 10, 1000)       # 24   same as (2**10) % 1000, but efficient

# math.pow() always returns float
math.pow(2, 3)         # 8.0  (float)
"Explicit is better than implicit." — The Zen of Python
code builder click a token to place it

Build a Python expression that raises base to the 8th power and assigns it to result.

your code will appear here...
8 result ** base = * +
Why: The correct expression is result = base ** 8. The assignment target goes on the left, then =, then the base, then the exponent operator **, then the exponent 8. Using * instead of ** would multiply rather than raise to a power, and + would only add 8 to the base.

Integer vs Float Results, and pow()

When both operands are integers, ** returns an integer. When either operand is a float, the result is a float. The three exponentiation tools differ in how they handle types, and only one supports a three-argument form.

MethodReturn type (int inputs)Three-argument formBest used for
**intNoGeneral-purpose exponentiation
pow(b, e)intYesWhen modular form may be needed
math.pow(b, e)floatNoWhen a float result is always required

Three-argument pow(base, exp, mod) computes (base ** exp) % mod using fast modular exponentiation internally. This is far more efficient than computing the full power first, and is the standard approach in cryptographic algorithms.

Return type
Returns int when both operands are integers; returns float when either operand is a float.
Best used for
General-purpose exponentiation in everyday expressions. Supports **= augmented assignment.
Return type
Returns int when both operands are integers; returns float when either is a float. Identical to ** for two arguments.
Best used for
When you may also need the three-argument modular form: pow(base, exp, mod). All three arguments must be integers when using the modulus parameter.
Return type
Always returns float, even for integer inputs. math.pow(2, 3) returns 8.0.
Best used for
When a float result is always required regardless of input types. Note: does not accept a third argument and will raise a TypeError if one is passed.
spot the bug click the line that contains the bug

The function below contains one bug. Click the line you think is wrong, then hit check.

1 def square_negative(n):
2 """Return the square of a negative number."""
3 return -n ** 2
4
5 print(square_negative(-4)) # expected: 16
6 # actual: -16
The fix: return (-n) ** 2. The ** operator has higher precedence than unary minus on the left, so -n ** 2 is evaluated as -(n ** 2), negating the result instead of squaring a negative base.

Negative and Fractional Exponents

A negative exponent computes the reciprocal of the positive power: b ** -n equals 1 / (b ** n). A fractional exponent computes a root: an exponent of 0.5 gives the square root, 1/3 the cube root.

python
# Negative exponents — result is always float
2 ** -1              # 0.5    (1 / 2)
2 ** -3              # 0.125  (1 / 8)
10 ** -2             # 0.01   (1 / 100)

# Square and cube roots via fractional exponent
16  ** 0.5           # 4.0
8   ** (1/3)         # 2.0
27  ** (1/3)         # 3.0
125 ** (1/3)         # 4.999999999999999  (floating-point imprecision)

# Precedence: ** binds tighter than unary minus on the left
-2 ** 4              # -16  (same as -(2 ** 4))
(-2) ** 4            # 16   (use parentheses for a negative base)

Edge Cases Worth Knowing

A small set of inputs produce results that surprise many programmers the first time they encounter them. Each case below has a specific reason rooted in Python's type system or IEEE 754 floating-point rules.

python
# 0 ** 0 — Python returns 1 by convention (undefined in mathematics)
0 ** 0               # 1
0 ** 0.0             # 1.0

# 0.0 ** negative — raises ZeroDivisionError (division by zero)
# 0.0 ** -1          # ZeroDivisionError: 0.0 cannot be raised to a negative power

# Negative base with fractional exponent — Python 3 returns a complex number
# (the ** operator promotes to complex rather than raising an error)
(-8) ** (1/3)        # (1.0000000000000002+1.7320508075688772j)  — NOT -2.0
(-8) ** 0.5          # (2.220446049250313e-16+2.8284271247461903j)

# math.pow() raises ValueError for a negative base with a fractional exponent
# import math
# math.pow(-8, 1/3)  # ValueError: math domain error

# Use the unary minus trick for odd integer roots of negative numbers
-(8 ** (1/3))        # -2.0  (manually negate after computing on the positive base)
Why does (-8) ** (1/3) return a complex number?

In Python 3, the ** operator follows standard complex arithmetic: any real number raised to a non-integer power can yield a complex result when the base is negative. The interpreter does not assume you want the real cube root; it computes the principal value. If you need the real cube root of a negative number, negate after computing on the positive base: -(8 ** (1/3)). For complex number exponentiation more broadly, use Python's built-in complex type or the cmath module.

Precision-critical work: use the Decimal module

When floating-point imprecision cannot be tolerated — financial calculations, for example — use Python's decimal.Decimal type. It supports the ** operator and delivers user-controlled precision without IEEE 754 rounding artifacts:

python
from decimal import Decimal, getcontext
getcontext().prec = 50          # set precision to 50 significant digits

Decimal('1.1') ** 2             # Decimal('1.21')  — exact
1.1 ** 2                        # 1.2100000000000002  — float imprecision

The Three-Argument pow() and Why It Matters

The three-argument form pow(base, exponent, modulus) does not simply compute base ** exponent and then take the remainder. Internally Python uses the binary exponentiation algorithm (also called fast exponentiation or exponentiation by squaring), which reduces a large power computation to a series of squarings and multiplications — so the intermediate numbers never grow large. All three arguments must be integers, and the exponent cannot be negative when a modulus is provided.

python
# Efficient modular exponentiation — used in RSA and Diffie-Hellman
pow(2, 10, 1000)          # 24     — (2**10) % 1000, but computed efficiently
pow(65537, 2048, 3233)    # fast   — RSA-style computation on large integers

# These will raise exceptions:
# pow(2.5, 3, 7)           # TypeError: all three arguments must be integers
# pow(2, -1, 7)            # ValueError: base is not invertible for given modulus
# pow(2+3j, 2, 5)          # TypeError: complex modulo not supported

How Python's ** Operator Works Internally

When Python evaluates x ** y, it calls the __pow__ method on the left operand. If that method returns NotImplemented, Python calls __rpow__ on the right operand. This means any class can support the ** operator by implementing these dunder methods — which is exactly how libraries like NumPy make array exponentiation work seamlessly with the same syntax.

python
# Python calls x.__pow__(y) first; if that returns NotImplemented,
# it calls y.__rpow__(x) as a fallback.
(2).__pow__(8)             # 256  — same as 2 ** 8
(2).__pow__(8, 1000)       # 256 % 1000 = 256  — __pow__ also accepts a modulus

# A minimal custom class that supports **
class Scalar:
    def __init__(self, value):
        self.value = value
    def __pow__(self, exponent):
        return Scalar(self.value ** exponent)
    def __repr__(self):
        return f"Scalar({self.value})"

Scalar(3) ** 4             # Scalar(81)
Python ** operator precedence — ** binds tighter than *, /, +, and unary minus on the left, so -2 ** 4 evaluates as -(2 ** 4), not (-2) ** 4.

How to Use Exponentiation in Python

Follow these steps to raise numbers to a power correctly, choose the right method, and avoid common precedence mistakes.

  1. Use the ** operator for standard exponentiation

    Write base ** exponent to raise the base to the given power. For integer operands the result is an integer. Use **= to update a variable in place: x **= 2 squares x.

  2. Use pow() when you may need modular exponentiation

    Call pow(base, exponent) for the same result as **. When you also need a modulus, use the three-argument form: pow(base, exponent, modulus). Python uses the fast exponentiation algorithm internally, making this far more efficient than computing the full power and then taking the remainder.

  3. Use fractional exponents for roots, with parentheses

    Raise a number to a fractional power to compute roots: 16 ** 0.5 is the square root, 8 ** (1/3) is the cube root. Always wrap fractional exponents in parentheses so Python does not misinterpret operator order. Results are floats and may carry small imprecision; use round() or math.isclose() for comparisons.

The Python Software Foundation's language reference documents that ** groups right-to-left and that unary minus has lower precedence — meaning -x ** n always means -(x ** n). When intent matters, use parentheses. — Python Language Reference: Expressions

Python Learning Summary Points

  1. The ** operator is right-associative with higher precedence than *, /, +, and -. When mixing operators or using a negative base, use parentheses to make intent explicit.
  2. Built-in pow() matches ** for two arguments and adds efficient modular exponentiation with a third argument. math.pow() always returns a float and has no three-argument form.
  3. Negative exponents return the reciprocal as a float. Fractional exponents compute roots but may carry small floating-point errors that require round() or math.isclose() for comparisons.
  4. 0 ** 0 returns 1 in Python by convention. Raising 0.0 to a negative power raises ZeroDivisionError. A negative base with a fractional exponent returns a complex number — not an error — under **, while math.pow() raises ValueError in that case.
  5. Three-argument pow(base, exp, mod) uses binary exponentiation internally, keeping intermediate values small. All three arguments must be integers; a float in any position raises TypeError.
  6. For precision-critical calculations where floating-point rounding is not acceptable, use decimal.Decimal with a configured precision context instead of ** on floats.
  7. Python evaluates x ** y by calling x.__pow__(y). If that returns NotImplemented, Python falls back to y.__rpow__(x). Implementing these dunder methods lets any class support the ** operator transparently.

Exponentiation appears throughout Python work: computing areas and volumes, applying compound interest formulas, generating powers of two for bitmasks and buffer sizes, and forming the backbone of cryptographic algorithms through modular exponentiation.

check your understanding question 1 of N

Frequently Asked Questions

The exponentiation operator in Python is **. It raises the left operand to the power of the right operand. For example, 2 ** 3 evaluates to 8.

The ** operator and built-in pow() return the same result for two arguments. pow() also accepts a third argument for modular exponentiation: pow(base, exp, mod). math.pow() always returns a float, while ** and built-in pow() preserve integer types when both operands are integers.

Use a fractional exponent of 0.5: value ** 0.5. For example, 16 ** 0.5 returns 4.0. You can also use math.sqrt() or math.pow(value, 0.5).

Python applies ** before unary minus on the left side, so -2 ** 2 is evaluated as -(2 ** 2) = -4. To square a negative number, use parentheses: (-2) ** 2 returns 4.

Modular exponentiation computes (base ** exponent) % modulus efficiently without computing the full power first. In Python, use the three-argument form: pow(base, exponent, modulus). This is commonly used in cryptography where the numbers involved are very large integers.

In Python, 0 ** 0 returns 1 and 0 ** 0.0 returns 1.0. This is a computational convention rather than a mathematical truth — zero to the power of zero is technically undefined in mathematics — but Python follows the same convention as most other languages and the Python Software Foundation documentation. Raising 0.0 to a negative power raises ZeroDivisionError.

In Python 3, raising a negative number to a non-integer exponent follows complex arithmetic and returns the principal complex root rather than a real number. This is correct mathematical behavior — Python does not assume you want the real cube root. To get the real cube root of a negative number, negate after computing on the positive base: -(8 ** (1/3)) returns -2.0. Note also that math.pow() raises ValueError in this case rather than returning a complex number.

For comparisons, use math.isclose() or round() rather than ==. For example, 125 ** (1/3) returns 4.999... rather than exactly 5. For calculations where floating-point rounding is unacceptable — such as financial math — use decimal.Decimal with a configured precision context, which supports ** and delivers exact arithmetic.

The three-argument pow(base, exp, mod) raises a TypeError if any argument is a float rather than an integer. It raises ValueError if the exponent is negative and the base is not invertible for the given modulus, or if complex numbers are used with a modulus. Two-argument pow(base, exp) and the ** operator raise ZeroDivisionError when 0.0 is raised to a negative power.

When Python evaluates x ** y, it calls x.__pow__(y). If that method returns NotImplemented, Python falls back to y.__rpow__(x). This protocol allows any class to support ** by implementing these dunder methods — which is how libraries like NumPy make array exponentiation work with the same operator syntax.

Certificate of Completion
Final Exam
Pass mark: 80% · Score 80% or higher to receive your certificate

Enter your name as you want it to appear on your certificate, then start the exam. Your name is used only to generate your certificate and is never transmitted or stored anywhere.

Question 1 of 10