`range(n)`, `range(start, stop)` and `range(start, stop, step)` all require their bounds to be resolvable at compile time (literals or constants), or you can use `range(n, bound=MAX)` to iterate a runtime value up to a fixed upper bound.
@external
@pure
def factorial(n: uint256) -> uint256:
result: uint256 = 1
for i in range(1, 21):
if i > n:
break
result *= i
return result`break` and `continue` work inside `for` loops just like in Python, letting you exit early once a condition is met.