Each element squared in a first loop, then square-rooted in a second loop. The trace shows squares filling up completely before the second loop starts building roots, making the two-pass structure explicit.

By hand

Two separate loops over values: the first squares each element into squares; the second takes math.sqrt and rounds to two decimal places into roots.

naive.py
Replay: real traced execution (multi-file project)
import math
values = [1, 2, 3, 4, 5, 6]
squares = []
for v in values:
    squares.append(v * v)
roots = []
for v in values:
    roots.append(round(math.sqrt(v), 2))
print('RESULT:', (squares, roots))
  1. import math

    1import math2values = [1, 2, 3, 4, 5, 6]
  2. values ← [1, 2, 3, 4, 5, 6]

    1import math2values = [1, 2, 3, 4, 5, 6]3squares = []
    values this step[1, 2, 3, 4, 5, 6]values
  3. squares ← []

    2values = [1, 2, 3, 4, 5, 6]3squares = []4for v in values:
    values this step[]squares
  4. v ← 1, squares ← [1]

    pass 1 of 6
    3squares = []4for v in values:5    squares.append(v * v)6roots = []
    values this step1v[] [1]squares
    All 6 passes — pass 1 is the card above
    passvsquares
    11[] [1]
    21 2[1] [1, 4]
    32 3[1, 4] [1, 4, 9]
    43 4[1, 4, 9] [1, 4, 9, 16]
    54 5[1, 4, 9, 16] [1, 4, 9, 16, 25]
    65 6[1, 4, 9, 16, 25] [1, 4, 9, 16, 25, 36]
  5. for v in values:

    3squares = []4for v in values:5    squares.append(v * v)
  6. roots ← []

    5    squares.append(v * v)6roots = []7for v in values:
    values this step[]roots
  7. v ← 1, roots ← [1.0]

    pass 1 of 6
    6roots = []7for v in values:8    roots.append(round(math.sqrt(v), 2))9print('RESULT:', (squares, roots))
    values this step6 1v[] [1.0]roots
    All 6 passes — pass 1 is the card above
    passvroots
    16 1[] [1.0]
    21 2[1.0] [1.0, 1.41]
    32 3[1.0, 1.41] [1.0, 1.41, 1.73]
    43 4[1.0, 1.41, 1.73] [1.0, 1.41, 1.73, 2.0]
    54 5[1.0, 1.41, 1.73, 2.0] [1.0, 1.41, 1.73, 2.0, 2.24]
    65 6[1.0, 1.41, 1.73, 2.0, 2.24] [1.0, 1.41, 1.73, 2.0, 2.24, 2.45]
  8. for v in values:

    6roots = []7for v in values:8    roots.append(round(math.sqrt(v), 2))
  9. stdout ← RESULT: ([1, 4, 9, 16, 25, 36], [1.0, 1.41, 1.73, 2.0, 2.24, 2.45])

    8    roots.append(round(math.sqrt(v), 2))9print('RESULT:', (squares, roots))
    values this stepRESULT: ([1, 4, 9, 16, 25, 36], [1.0, 1.41, 1.73, 2.0, 2.24, 2.45])stdout

With NumPy

a ** 2 raises every element to the power 2 in one call. np.sqrt(a) applies the square-root ufunc elementwise. Both operations return new arrays without an explicit loop. The snapshot shows both results in labeled blocks.

library.py
import numpy as np

values = [1, 2, 3, 4, 5, 6]
a = np.array(values)
squares = a ** 2
roots = np.sqrt(a)
sq_vals = squares.tolist()
rt_vals = [round(v, 2) for v in roots.tolist()]
print(f'squares: shape: {squares.shape} dtype: {squares.dtype} values: {sq_vals}')
print(f'roots: shape: {roots.shape} dtype: {roots.dtype} values: {rt_vals}')
print('RESULT:', (sq_vals, rt_vals))
squares: shape: (6,) dtype: int64 values: [1, 4, 9, 16, 25, 36]
roots: shape: (6,) dtype: float64 values: [1.0, 1.41, 1.73, 2.0, 2.24, 2.45]
RESULT: ([1, 4, 9, 16, 25, 36], [1.0, 1.41, 1.73, 2.0, 2.24, 2.45])

Implementation notes

  • np.sqrt, np.sin, np.log, and similar functions are ufuncs (universal functions). A ufunc applies a C-level scalar kernel to every element of its input array, returning an array of the same shape — the NumPy equivalent of the hand-written loop here.
  • For this integer array, a ** 2 keeps the integer dtype (int64), while np.sqrt(a) promotes to float64. np.sqrt preserves an input's float dtype — e.g. a float32 input stays float32.
  • Shape, dtype, and values are shown explicitly here because ndarray.__repr__ output varies with NumPy version and print options.