Skip to Content
ReferenceLimits and errors

Sandboxed execution

Every execution is metered, there is no unmetered mode. Both shipped engines, the browser runtime and the CLI’s native engine, run every script under these limits, and edge actor groups can override them per field.

LimitValueWhat hitting it raises
Max call depth256RecursionError
Max operations100,000,000RuntimeError
Max live objects100,000MemoryError

The op-limit RuntimeError cannot be caught. The except handler is entered but its first operation re-raises because the budget is still exhausted.

def loop(n): return loop(n + 1) try: loop(0) except RecursionError: print("hit max depth")
Output · expected
hit max depth

Integer width

Integers are two-tier:

  • Inline (fast). 48-bit signed, packed into the NaN-boxed value. Range -140_737_488_355_328 to 140_737_488_355_327 (-2^47 to 2^47 - 1). One ALU op per arithmetic, no allocation.
  • Wide (slow). 128-bit, heap-allocated. Used automatically when a literal exceeds the inline range or inline arithmetic overflows.

Promotion is automatic and invisible. Past ±2^127, arithmetic raises OverflowError. Integers are not unbounded: wider than 128 bits is out of scope by design.

print(140737488355327) # inline, fast path print(2 ** 47) # auto-promotes to the wide path print(2 ** 100) try: print(2 ** 127) # past the 128-bit cap except OverflowError: print("overflow")
Output · expected
140737488355327 140737488355328 1267650600228229401496703205376 overflow

pow(a, b, m) with a modulus is supported, but the modulus must be at most 2^63. Larger values raise ValueError, because the intermediate multiply would overflow 128 bits.

Source and token limits

Source must be under 10 MiB. Larger input is rejected at lex time. The remaining caps prevent asymmetric inputs, small sources that would produce huge parse trees or instruction streams:

LimitValueDiagnostic
Source size10 MiBsource file exceeds maximum size (10 MiB)
Indent depth100indentation depth exceeds maximum (100)
F-string nesting depth200f-string nesting depth exceeds maximum (200)
Expression nesting depth200expression too deeply nested
Instructions, names, or constants per chunk65,535program too large: exceeded the 65535 instruction, name, or constant limit
Call arguments255 positional, 255 keywordtoo many arguments in call (max 255 positional and 255 keyword)
Native imports per module256too many native imports (max 256 per module)

Separately, repr output is truncated with a trailing , ... past 1,000,000 characters, so printing a huge structure cannot exhaust memory.

Compile-time errors

Syntax and resolution errors are reported as diagnostics with byte offsets into the source, rendered with line, column, and a caret preview. They are caught before any code runs and cannot be caught by try / except.

DiagnosticCause
expected X, got 'Y'Unexpected token
'(' was never closed (or '[' / '{')Bracket opened with no matching closer
')' does not match '[', expected ']'Wrong closer kind for the innermost opener
unexpected ')', no matching openerCloser with no opener on the stack
unterminated string literalString missing its closing quote
unterminated triple-quoted string literalTriple-quoted string hit EOF
f-string was never closedF-string body hit EOF before its close
inconsistent indentation: mixing tabs and spacesIndent mixes both whitespace kinds
unindent does not match any outer indentation levelDedent lands between two outer levels
integer literal too large to represent (max ±2^127)Literal past the 128-bit cap
'break' outside loop / 'continue' outside loopMisplaced control keyword
default 'except:' must be lastBare except not at the end
expression too deeply nestedPast the expression depth cap
program too large: exceeded the 65535 instruction, name, or constant limitPast the chunk cap

Import failures are compile-time diagnostics too, including modules Edge Python does not ship (os, sys, asyncio). See Modules for those message formats.

Runtime errors

Runtime errors raise as typed exceptions, catchable with try / except.

ClassWhen
TypeErrorWrong operand or argument type
ValueErrorRight type, invalid value
AttributeErrorAttribute not found on the object
NameErrorUndefined name
ZeroDivisionErrorDivision or modulo by zero
OverflowErrorInteger arithmetic past ±2^127
KeyErrorDict or set lookup miss
IndexErrorSequence index out of range
StopIterationIterator exhausted
AssertionErrorFailed assert
TimeoutErrorwith_timeout deadline expired
CancelledErrorCoroutine cancelled by cancel()
SystemExitraise SystemExit(code). Uncaught, the host exits with that code
RecursionErrorPast the call-depth limit
MemoryErrorPast the live-object limit
RuntimeErrorPast the op limit, an import cycle, input() without host data, or an internal invariant

Every entry in the table fires from ordinary code:

def show(f): try: f() except Exception as e: print(type(e).__name__ + ":", e) show(lambda: 1 + "x") show(lambda: int("abc")) show(lambda: {}["missing"]) show(lambda: [1][5]) show(lambda: nope) show(lambda: 1 % 0) def fail(): assert 1 == 2, "math broke" show(fail)
Output · expected
TypeError: unsupported operand type(s) for +: 'int' and 'str' ValueError: int(): invalid literal KeyError: 'missing' IndexError: list index out of range NameError: name 'nope' is not defined ZeroDivisionError: division by zero AssertionError: math broke

SystemExit needs its own except clause, and TimeoutError fires when a with_timeout deadline expires:

try: raise SystemExit(3) except SystemExit: print("caught exit") async def slow(): sleep(1) try: run(with_timeout(0.01, slow())) except TimeoutError: print("timed out")
Output · expected
caught exit timed out

A user raise X re-raises whatever class or instance X is. Raising a value that does not derive from BaseException (a str, an int) gives TypeError.

Exception hierarchy

except walks parent links in a curated tree rooted at BaseException:

  • Exception sits under BaseException. Everything catchable in normal code derives from Exception.
  • LookupError groups IndexError and KeyError. ArithmeticError groups OverflowError and ZeroDivisionError. RuntimeError parents RecursionError and NotImplementedError.
  • OSError, NameError, StopIteration, StopAsyncIteration, AssertionError, MemoryError, and TimeoutError sit directly under Exception.
  • SystemExit and CancelledError sit directly under BaseException, so except Exception does not catch them. Use their own name or a bare except.
try: raise RuntimeError("oops") except Exception as e: print("caught via parent:", e) try: [][0] except Exception: print("caught IndexError as Exception")
Output · expected
caught via parent: oops caught IndexError as Exception

User-defined classes do not join the built-in tree, but they support inheritance among themselves: except UserBase catches a raised UserSub when UserSub inherits from UserBase. For raise X from Y and chaining, see Control flow.

Exception arguments

Caught exceptions expose their constructor arguments as e.args, a tuple. raise X("msg") and raise X(a, b) carry through, runtime-raised errors carry their message as a single arg, and a bare raise X produces an empty tuple.

try: raise TypeError("bad input") except TypeError as e: print(e.args) try: 1 / 0 except ZeroDivisionError as e: print(e.args) try: raise ValueError except ValueError as e: print(e.args)
Output · expected
('bad input',) ('division by zero',) ()

Catching errors

def safe(f, x): try: return f(x) except TypeError: return "type" except ValueError: return "value" except ZeroDivisionError: return "zero" except: return "other" print(safe(lambda x: 1 / x, 0)) print(safe(lambda x: int(x), "abc")) print(safe(lambda x: len(x), 42))
Output · expected
zero value type

Environmental errors

Failures that happen before the source reaches the compiler surface as plain text, uncatchable from script code, with no line or column to anchor to:

ErrorWhen
input rejected: invalid utf-8 at byte NHost input bytes are not valid UTF-8
source file exceeds maximum size (10 MiB)Source over the lex-time cap

Handle these at the embedder layer (path validation, encoding, size check) before invoking the compiler.

Behavioral notes

A few supported operations have implementation-defined or by-design behavior worth knowing:

  • Set iteration and repr order. Sets store elements in a hash table, so iteration and repr follow hash order, not insertion order. Do not rely on it. {3, 1, 2} may repr as {3, 2, 1}.
  • is on numbers. Inline integers are compared by value, so a = 1000; b = 1000; a is b is True. Use == for value equality and reserve is for None and identity checks.
  • str.casefold. Simple lowercasing without full Unicode case-fold expansion: 'ß'.casefold() stays 'ß' rather than expanding to 'ss'.

The is note, demonstrated:

a = 1000 b = 1000 print(a is b) print(a == b)
Output · expected
True True

Determinism

Same source plus same input gives the same output across runs and architectures (x86_64, aarch64, wasm32). There is no time, randomness, threading, or OS interaction in the core language. Heap-slot reuse is the only nondeterminism, and it is observable through id(x) only, never through ==, repr, or any other operation.

Last updated on