PONYλM2Modula-2

Python.CodeCompared.To/Mojo

An interactive executable cheatsheet comparing Python and Mojo

Python 3.14 Mojo 1.0.0
Output & Running
Hello, World
print("Hello, World!")
def main() raises: print("Hello, World!")

Mojo's print() works exactly like Python's: it accepts any number of values, joins them with spaces, and adds a newline. The entry point is def main() raises: rather than the implicit top-level execution Python uses. The raises qualifier allows the function to propagate errors freely — a useful habit for main() so any called function can use raise without an additional declaration.

Printing multiple values
name = "Alice" age = 30 print(name, age) print(name, "is", age, "years old")
def main() raises: var name = "Alice" var age = 30 print(name, age) print(name, "is", age, "years old")

Mojo's print() accepts multiple comma-separated arguments and joins them with spaces — identical to Python's print(). The only visible difference in this example is the var keyword on the first two lines. The multi-argument form is the idiomatic one, and it makes clear that no implicit type coercion is occurring.

Comments
# Single-line comment print("Hello") # inline comment """ Multi-line string often used as a block comment """
def main() raises: # Single-line comment print("Hello") # inline comment # Mojo has no block comment syntax — # use consecutive line comments instead.

Mojo uses # for line comments, identical to Python. A triple-quoted string on a line of its own is a docstring rather than a comment — the compiler reads it and even lints its wording — so use consecutive line comments for longer explanations.

String conversion for output
value = 42 pi = 3.14159 print("Value: " + str(value)) print("Pi: " + str(pi))
def main() raises: var value: Int = 42 var pi: Float64 = 3.14159 print("Value: " + String(value)) print("Pi: " + String(pi))

Python's built-in str() converts any value to a string for concatenation. Mojo uses an explicit String(value) constructor call that works the same way. Both produce a human-readable string representation. Alternatively, passing values as separate print() arguments avoids the conversion step entirely and is the more common Mojo idiom.

Variables & Types
Variable declaration
name = "Alice" count = 42 active = True print(name, count, active)
def main() raises: var name = "Alice" var count = 42 var active = True print(name, count, active)

Mojo introduces a new variable with the var keyword. Bare assignment to an undeclared name still compiles, but the compiler warns that implicit declaration is deprecated — where Python accepts it silently. Types are inferred from the initial value exactly as in Python, so no annotation is needed when the type is obvious from context.

Type annotations
name: str = "Alice" age: int = 30 score: float = 9.5 print(name, age, score)
def main() raises: var name: String = "Alice" var age: Int = 30 var score: Float64 = 9.5 print(name, age, score)

Python's type hints (name: str = "Alice") are advisory — the interpreter ignores them at runtime. Mojo's type annotations are enforced at compile time: assigning the wrong type is a compile error. Mojo uses String (capital S) rather than Python's str, and Float64 rather than float. Plain Int is machine-word-sized (64-bit on modern hardware).

Reassignment
counter = 0 counter = 10 counter += 5 print(counter) # 15
def main() raises: var counter = 0 counter = 10 counter += 5 print(counter) # 15

Once a var variable is declared, reassignment uses plain = without the var keyword — the same as Python. Compound assignment operators (+=, -=, *=, /=) all work as expected. The difference from Python is only at the point of initial declaration, where var is required.

Constants
# Python has no built-in constant mechanism; # convention is to use ALL_CAPS names (but nothing enforces it) MAX_CONNECTIONS = 100 APP_NAME = "MyApp" print(MAX_CONNECTIONS, APP_NAME)
comptime MAX_CONNECTIONS: Int = 100 comptime APP_NAME: String = "MyApp" def main() raises: print(MAX_CONNECTIONS, APP_NAME)

Mojo's comptime keyword creates a compile-time constant — the value is substituted at compile time and cannot be changed at runtime. (Older material writes alias, which still compiles with a deprecation warning.) Python only has a naming convention (ALL_CAPS) that other programmers are supposed to respect; nothing prevents reassignment. A comptime value can appear in type parameter positions where only compile-time-known values are allowed, such as SIMD vector widths.

Type conversion
whole = 10 decimal = float(whole) / 3.0 text = str(whole) print(decimal) print(text + " items")
def main() raises: var whole: Int = 10 var decimal: Float64 = Float64(whole) / 3.0 var text: String = String(whole) print(decimal) print(text + " items")

Python's built-in conversion functions (int(), float(), str()) accept almost any value and handle conversion automatically. Mojo uses explicit constructor-style calls: Float64(whole), String(whole). Mojo never performs implicit numeric coercion — mixing Int and Float64 in an expression without an explicit conversion is a compile error.

Strings
String basics
greeting = "Hello" name = "World" combined = greeting + ", " + name + "!" print(combined) print(len(combined))
def main() raises: var greeting = "Hello" var name = "World" var combined = greeting + ", " + name + "!" print(combined) print(combined.byte_length())

Mojo's String type behaves much like Python's: + concatenates strings, and len() returns the byte length. All Mojo strings are UTF-8 encoded and immutable by default, matching Python's string immutability. One key difference: Mojo's len() counts bytes, not Unicode code points, so multi-byte characters produce a larger count than Python's character-counting len().

Case conversion
message = "Hello, World!" print(message.upper()) print(message.lower())
def main() raises: var message = "Hello, World!" print(message.upper()) print(message.lower())

Mojo's String type provides the same .upper() and .lower() methods as Python. Both return a new string without modifying the original — string immutability is the same in both languages. These method names were intentionally chosen to match Python's, easing the transition.

Find and replace
sentence = "The quick brown fox" print("quick" in sentence) print(sentence.find("fox")) print(sentence.replace("fox", "cat"))
def main() raises: var sentence = "The quick brown fox" print("quick" in sentence) print(sentence.find("fox")) print(sentence.replace("fox", "cat"))

Mojo's .find() returns the byte index of the first occurrence, or -1 if not found — the same semantics as Python's str.find(). Containment is the in operator, exactly as in Python. The .replace() method replaces all occurrences, matching Python's behavior.

Strip and split
padded = " hello " cleaned = padded.strip() print(cleaned) words = "one two three".split() print(len(words))
def main() raises: var padded = " hello " var cleaned = padded.strip() print(cleaned) var words = "one two three".split() print(len(words))

.strip() removes leading and trailing whitespace and .split() without arguments splits on whitespace runs — both behave identically to Python. Mojo's .split() returns a List[String] rather than Python's list, but the usage is the same: index it with [i] or iterate with a for loop.

Prefix and suffix checks
filename = "report.csv" print(filename.startswith("report")) print(filename.endswith(".csv")) print(filename.endswith(".txt"))
def main() raises: var filename = "report.csv" print(filename.startswith("report")) print(filename.endswith(".csv")) print(filename.endswith(".txt"))

.startswith() and .endswith() have identical names and semantics in Mojo and Python. Both return a Bool value. This is one of the areas where Mojo's Python-superset design makes the transition completely seamless — the code is character-for-character identical.

Numbers
Integer types
# Python has one integer type: arbitrary precision small = 42 large = 10 ** 100 # googol — no overflow in Python print(type(small).__name__) print(small)
def main() raises: var machine_word: Int = 42 var signed_64: Int64 = 1_000_000_000 var signed_32: Int32 = 32_767 var signed_8: Int8 = 127 print(machine_word) print(signed_64)

Python's int has arbitrary precision — it grows as large as memory allows. Mojo's integer types have fixed bit widths: Int8, Int16, Int32, Int64, and their unsigned counterparts (UInt8, etc.). Plain Int is machine-word-sized (64-bit on a 64-bit system). Unlike Python, Mojo integers can overflow — an Int8 holding 128 wraps in release builds and panics in debug builds.

Float types
# Python has one float type: IEEE 754 double-precision (64-bit) pi = 3.14159265358979 tau = 6.28318530717958 print(type(pi).__name__) print(pi + tau)
def main() raises: var pi: Float64 = 3.14159265358979 var approx: Float32 = 3.14 var half_prec: Float16 = 3.14 print(pi) print(approx)

Python's float is always IEEE 754 double-precision (64-bit). Mojo provides three floating-point types: Float16, Float32, and Float64. Floating-point literals default to Float64, matching Python's behavior. Use Float32 or Float16 to match the precision expected by GPU and SIMD hardware in machine-learning workloads — the primary domain Mojo targets.

Arithmetic operators
a = 17 b = 5 print(a + b) # 22 print(a - b) # 12 print(a * b) # 85 print(a // b) # 3 (floor division) print(a % b) # 2 (modulo) print(a ** 2) # 289 (exponentiation)
def main() raises: var a = 17 var b = 5 print(a + b) # 22 print(a - b) # 12 print(a * b) # 85 print(a // b) # 3 (floor division) print(a % b) # 2 (modulo) print(a ** 2) # 289 (exponentiation)

Mojo's arithmetic operators are identical to Python's: +, -, *, // (floor division), % (modulo), and ** (exponentiation). This is intentional — Mojo is a Python superset. The difference is that Mojo resolves these at compile time when types are known, potentially emitting a single CPU instruction instead of a Python bytecode dispatch chain.

Bool type
is_active = True is_done = False print(is_active) print(not is_active) print(is_active and not is_done)
def main() raises: var is_active: Bool = True var is_done: Bool = False print(is_active) print(not is_active) print(is_active and not is_done)

Mojo's Bool type uses the same capitalized literals (True, False) and the same English-word operators (and, or, not) as Python. This is one of the areas where the transition from Python to Mojo is effortless — the code looks identical. The difference is that Bool in Mojo is a strictly typed value, not a subclass of int as it is in Python.

Collections
List basics
numbers = [10, 20, 30, 40] print(numbers[0]) # 10 print(numbers[-1]) # 40 print(len(numbers)) # 4
def main() raises: var numbers: List[Int] = [10, 20, 30, 40] print(numbers[0]) # 10 print(numbers[len(numbers) - 1]) # 40 print(len(numbers)) # 4

Mojo's List[T] is a typed, resizable array. The literal [10, 20, 30, 40] works as in Python once the variable's element type is declared, and every element must have that type. Lists are zero-indexed like Python's, but a negative index is rejected — numbers[-1] is a compile error that suggests numbers[len(numbers) - 1].

List iteration
fruits = ["apple", "banana", "cherry"] for fruit in fruits: print(fruit) for index, fruit in enumerate(fruits): print(index, fruit)
def main() raises: var fruits: List[String] = ["apple", "banana", "cherry"] for fruit in fruits: print(fruit) for index, fruit in enumerate(fruits): print(index, fruit)

Both loops carry over unchanged: for fruit in fruits yields each element, and enumerate() yields index-and-value pairs exactly as Python's does.

Dict basics
scores = {"Alice": 95, "Bob": 87} print(scores["Alice"]) # 95 scores["Charlie"] = 92 print(len(scores)) # 3
def main() raises: var scores: Dict[String, Int] = {"Alice": 95, "Bob": 87} print(scores["Alice"]) # 95 scores["Charlie"] = 92 print(len(scores)) # 3

Dict[K, V] is Mojo's typed hash map. The dict literal {"Alice": 95, "Bob": 87} works as in Python once the variable declares its key and value types. Subscript access and assignment (dict["key"] = value) work identically to Python. Accessing a missing key raises an error in Mojo — use .get() for safe access with a default value.

Safe dict access
config = {"host": "localhost", "port": "5432"} host = config.get("host", "127.0.0.1") timeout = config.get("timeout", "30") print(host) print(timeout)
def main() raises: var config = Dict[String, String]() config["host"] = "localhost" config["port"] = "5432" var host = config.get("host", "127.0.0.1") var timeout = config.get("timeout", "30") print(host) print(timeout)

Mojo's Dict.get(key, default) returns the value for key if present, otherwise returns default — exactly the same semantics as Python's dict.get(key, default). This is one of the many areas where Mojo's Python-superset design means existing Python knowledge transfers directly without adjustment.

Dict iteration
grades = {"Alice": "A", "Bob": "B", "Charlie": "C"} for name, grade in grades.items(): print(name, grade)
def main() raises: var grades = Dict[String, String]() grades["Alice"] = "A" grades["Bob"] = "B" grades["Charlie"] = "C" for key in grades.keys(): var name = String(key) print(name, grades[name])

Python's dict.items() yields key-value pairs in a single loop. Mojo's Dict provides .keys() and .values() iterators, but not .items() in current versions. Iterating over .keys() yields references — copy each key with String(key) before using it as a subscript argument to avoid aliasing errors from the iterator holding a reference into the dict's own memory.

Control Flow
if / elif / else
score = 85 if score >= 90: print("A") elif score >= 80: print("B") elif score >= 70: print("C") else: print("F")
def main() raises: var score = 85 if score >= 90: print("A") elif score >= 80: print("B") elif score >= 70: print("C") else: print("F")

Mojo's conditional syntax is identical to Python's: if, elif, else, colon-terminated conditions, and indentation-based blocks. Comparison operators (>=, ==, !=, <, >, <=) are the same as Python's. This is a zero-adjustment area for Python programmers moving to Mojo.

for loop with range
for number in range(1, 6): print(number) for number in range(0, 10, 2): print(number)
def main() raises: for number in range(1, 6): print(number) for number in range(0, 10, 2): print(number)

Mojo's range(start, stop, step) is functionally identical to Python's: stop is exclusive and the three-argument form sets the step. The for number in range(...) syntax is the same in both languages. This example is shown to confirm that Python intuition transfers completely — the code is literally identical.

while loop
countdown = 5 while countdown > 0: print(countdown) countdown -= 1 print("Go!")
def main() raises: var countdown = 5 while countdown > 0: print(countdown) countdown -= 1 print("Go!")

Mojo's while loop syntax is identical to Python's. The only visible difference in this example is var countdown on the first line — once declared, countdown behaves exactly as in Python. Python's while/else carries over too: the else block runs when the loop ends without a break.

break and continue
for number in range(1, 11): if number % 2 == 0: continue if number > 7: break print(number)
def main() raises: for number in range(1, 11): if number % 2 == 0: continue if number > 7: break print(number)

break and continue behave identically in Python and Mojo — no adjustment needed. This example is shown specifically to confirm that Python loop-control intuition transfers directly. The code is character-for-character identical inside def main() raises:.

Looping with an index
colors = ["red", "green", "blue"] for index, color in enumerate(colors): print(index, color)
def main() raises: var colors: List[String] = ["red", "green", "blue"] for index, color in enumerate(colors): print(index, color)

Python's enumerate() carries over exactly: it yields index-and-value pairs, unpacked in the for header.

def Functions
Basic def function
def greet(name: str) -> str: return f"Hello, {name}!" print(greet("Alice"))
def greet(name: String) -> String: return "Hello, " + name + "!" def main() raises: print(greet("Alice"))

Mojo's def looks like Python's and checks what Python's does not: every parameter needs a type, a function that can raise must say raises, and both are enforced at compile time. The body uses the same return-value semantics as Python — the return keyword is required (unlike Ruby).

Default arguments
def greet(name: str = "World", punctuation: str = "!") -> str: return f"Hello, {name}{punctuation}" print(greet()) print(greet("Alice")) print(greet("Bob", "."))
def greet(name: String = "World", punctuation: String = "!") -> String: return "Hello, " + name + punctuation def main() raises: print(greet()) print(greet("Alice")) print(greet("Bob", "."))

Default argument syntax is nearly identical between Python and Mojo's def functions — the same param: Type = default form. A call may also name its arguments, as in Python: greet(punctuation=".") keeps the default name.

Multiple return values
def compute_bounds(numbers: list[int]) -> tuple[int, int]: return min(numbers), max(numbers) low, high = compute_bounds([3, 1, 4, 1, 5, 9, 2]) print(low, high)
def find_min_max(first: Int, second: Int, third: Int) -> Tuple[Int, Int]: var minimum = first var maximum = first if second < minimum: minimum = second if second > maximum: maximum = second if third < minimum: minimum = third if third > maximum: maximum = third return (minimum, maximum) def main() raises: var low, high = find_min_max(3, 7, 1) print(low, high)

Both languages return several values as a tuple and unpack it at the call site. Mojo spells the return type out — Tuple[Int, Int] — and the unpacking declares its names with var: var low, high = find_min_max(3, 7, 1).

Recursive functions
def factorial(number: int) -> int: if number <= 1: return 1 return number * factorial(number - 1) print(factorial(6))
def factorial(number: Int) -> Int: if number <= 1: return 1 return number * factorial(number - 1) def main() raises: print(factorial(6))

Recursive def functions look nearly identical in Python and Mojo. The only change here is the Mojo type name Int (capital I) instead of Python's int. Both languages use the same recursive call syntax.

fn Functions
Types are required, and enforced
# Python type hints are advisory — ignored at runtime def add(x: int, y: int) -> int: return x + y print(add(3, 4)) print(add(1.5, 2.5)) # Works — Python coerces float to int silently? No, it just runs
def add(left: Int, right: Int) -> Int: return left + right def main() raises: print(add(3, 4)) # add(1.5, 2.5) would be a compile-time error

Mojo and Python share the def keyword and mean quite different things by it. Python's type hints are advisory and ignored at runtime — def add(x: int, y: int) still happily accepts a float. Mojo requires the annotations and enforces them: passing the wrong type is a compile error, which is what allows dispatch with no runtime type checks. (Mojo used to spell the strict form fn and keep def loose; 1.0 removed fn and made def the strict one.)

Mutable parameters (mut)
# Python silently allows functions to mutate mutable arguments def double_in_place(numbers: list[int]) -> None: for index in range(len(numbers)): numbers[index] *= 2 data = [1, 2, 3] double_in_place(data) print(data) # [2, 4, 6]
def double_in_place(mut numbers: List[Int]): for index in range(len(numbers)): numbers[index] = numbers[index] * 2 def main() raises: var data = List[Int]() data.append(1) data.append(2) data.append(3) double_in_place(data) for index in range(len(data)): print(data[index])

Python silently allows a function to mutate any mutable object passed to it — there is no indication at the call site or in the signature that mutation will occur. Mojo requires the mut keyword on the parameter to declare that mutation is intended. Without mut, the parameter is borrowed immutably and cannot be modified. A reader can see from the signature whether a function mutates its arguments, making the contract explicit.

Functions that can raise
def parse_age(text: str) -> int: age = int(text) if age < 0: raise ValueError("Age cannot be negative") return age print(parse_age("25"))
def parse_age(text: String) raises -> Int: var age = atol(text) if age < 0: raise Error("Age cannot be negative") return age def main() raises: print(parse_age("25"))

In Python, any function can raise any exception at any time — nothing in the signature indicates this. A Mojo function must declare raises if it can propagate an error. This makes the error contract visible and compiler-checked. atol() is Mojo's built-in for parsing a String to Int — it raises automatically if parsing fails, which is why parse_age must also declare raises.

Function overloading
# Python uses isinstance() checks instead of overloading def describe(value: int | float) -> str: if isinstance(value, int): return f"Integer: {value}" return f"Float: {value}" print(describe(42)) print(describe(3.14))
def describe(value: Int) -> String: return "Integer: " + String(value) def describe(value: Float64) -> String: return "Float: " + String(value) def main() raises: print(describe(42)) print(describe(3.14))

Mojo supports function overloading: several def definitions with the same name but different parameter types. The compiler selects the correct version at compile time based on the argument types. Python achieves similar behavior by checking isinstance() at runtime — Mojo does the same dispatch statically with no runtime cost and without the branching code in the function body.

Structs vs Classes
Dataclass vs fieldwise_init struct
from dataclasses import dataclass @dataclass class Point: x: float y: float point = Point(3.0, 4.0) print(point.x, point.y)
@fieldwise_init struct Point(Copyable, Movable): var x: Float64 var y: Float64 def main() raises: var point = Point(3.0, 4.0) print(point.x, point.y)

Python's @dataclass and Mojo's @fieldwise_init both auto-generate a constructor that accepts one argument per field in declaration order. The critical difference is value semantics: assigning a Mojo struct copies it, whereas assigning a Python object copies a reference. Copyable and Movable are built-in traits that must be listed explicitly to enable copying and moving the struct.

Explicit initializer (__init__)
class Rectangle: def __init__(self, width: float, height: float): self.width = width self.height = height rect = Rectangle(3.0, 4.0) print(rect.width)
struct Rectangle: var width: Float64 var height: Float64 def __init__(out self, width: Float64, height: Float64): self.width = width self.height = height def main() raises: var rectangle = Rectangle(3.0, 4.0) print(rectangle.width)

Mojo structs use def __init__(out self, ...) for explicit constructors — the out self parameter indicates that this function initializes the struct rather than receiving a pre-allocated instance. Python's __init__(self, ...) receives an already-allocated object. Both set fields via self.field = value. Mojo field declarations (var width: Float64) must appear explicitly in the struct body before they can be assigned in __init__.

Instance methods
import math class Circle: def __init__(self, radius: float): self.radius = radius def area(self) -> float: return math.pi * self.radius ** 2 circle = Circle(5.0) print(round(circle.area(), 4))
from std.math import pi struct Circle: var radius: Float64 def __init__(out self, radius: Float64): self.radius = radius def area(self) -> Float64: return pi * self.radius * self.radius def main() raises: var circle = Circle(5.0) print(circle.area())

Read-only methods take self (borrowed, immutable) — matching Python's convention where self refers to the current instance. Mojo imports standard library items explicitly, as Python does, from under std: from std.math import pi. Methods are called with instance.method() and always require parentheses, even for zero-argument methods.

Mutating methods (mut self)
class Counter: def __init__(self): self.count = 0 def increment(self): self.count += 1 def value(self) -> int: return self.count counter = Counter() counter.increment() counter.increment() print(counter.value()) # 2
struct Counter: var count: Int def __init__(out self): self.count = 0 def increment(mut self): self.count += 1 def value(self) -> Int: return self.count def main() raises: var counter = Counter() counter.increment() counter.increment() print(counter.value()) # 2

In Python, any method can modify instance attributes — self is always a mutable reference. Mojo requires methods that modify the struct to declare mut self instead of self. Read-only methods use plain self (borrowed). This distinction is enforced by the compiler: calling a mut self method on an immutable binding is a compile error, making mutation intent explicit in the API.

String representation (write_to)
class Person: def __init__(self, name: str, age: int): self.name = name self.age = age def __str__(self) -> str: return f"Person({self.name}, {self.age})" person = Person("Alice", 30) print(person) # __str__ called automatically
struct Person(Writable): var name: String var age: Int def __init__(out self, name: String, age: Int): self.name = name self.age = age def write_to(self, mut writer: Some[Writer]): writer.write("Person(" + self.name + ", " + String(self.age) + ")") def main() raises: var person = Person("Alice", 30) print(String(person)) # String() still works; print(person) alone would too

Python calls __str__ automatically when an object is passed to print(). Mojo uses the Writable trait (listed in parentheses after the struct name) with a write_to(self, mut writer) method that writes into the writer rather than returning a String. String(instance) converts the struct to a string explicitly, but print(instance) also works directly since Person conforms to Writable.

Traits
Defining a trait
from abc import ABC, abstractmethod class Greetable(ABC): @abstractmethod def greet(self) -> str: ...
trait Greetable: def greet(self) -> String: ... def main() raises: pass

Mojo's trait keyword is the equivalent of Python's ABC with @abstractmethod. A trait declares required method signatures — structs that list the trait must implement every method, or the compiler rejects the code. Unlike Python ABCs, Mojo traits are checked at compile time with no runtime overhead. A trait may also give a method a default body, which a struct inherits unless it defines its own.

Implementing a trait
from abc import ABC, abstractmethod class Describable(ABC): @abstractmethod def describe(self) -> str: ... class Dog(Describable): def __init__(self, name: str): self.name = name def describe(self) -> str: return f"Dog named {self.name}" print(Dog("Rex").describe())
trait Describable: def describe(self) -> String: ... @fieldwise_init struct Dog(Describable, Copyable, Movable): var name: String def describe(self) -> String: return "Dog named " + self.name def main() raises: var dog = Dog("Rex") print(dog.describe())

A Mojo struct implements a trait by listing it in parentheses after the struct name, alongside built-in traits like Copyable and Movable. The compiler verifies that all trait methods are implemented — unlike Python ABCs, where a class can be instantiated with abstract methods still missing and only fail at runtime when those methods are called. Multiple traits are listed together in one set of parentheses.

Generic functions via traits
from abc import ABC, abstractmethod class Shape(ABC): @abstractmethod def area(self) -> float: ... class Square(Shape): def __init__(self, side: float): self.side = side def area(self) -> float: return self.side ** 2 class Circle(Shape): def __init__(self, radius: float): self.radius = radius def area(self) -> float: import math return math.pi * self.radius ** 2 for shape in [Square(4.0), Circle(3.0)]: print(round(shape.area(), 4))
from std.math import pi trait Shape: def area(self) -> Float64: ... @fieldwise_init struct Square(Shape, Copyable, Movable): var side: Float64 def area(self) -> Float64: return self.side * self.side @fieldwise_init struct Circle(Shape, Copyable, Movable): var radius: Float64 def area(self) -> Float64: return pi * self.radius * self.radius def print_area[ShapeType: Shape](shape: ShapeType): print(shape.area()) def main() raises: var square = Square(4.0) var circle = Circle(3.0) print_area(square) print_area(circle)

Mojo achieves polymorphism through parametric functions: def print_area[ShapeType: Shape](shape: ShapeType) accepts any type ShapeType that implements the Shape trait. The square-bracket [ShapeType: Shape] is a compile-time type parameter — the compiler generates a specialized version for each concrete type used, like C++ templates. Python achieves the same result through runtime duck typing. Mojo's approach produces identical behavior with zero runtime dispatch overhead.

Comparable trait
from functools import total_ordering @total_ordering class Temperature: def __init__(self, celsius: float): self.celsius = celsius def __eq__(self, other) -> bool: return self.celsius == other.celsius def __lt__(self, other) -> bool: return self.celsius < other.celsius boiling = Temperature(100.0) body = Temperature(37.0) print(boiling > body) # True
@fieldwise_init struct Temperature(Copyable, Movable, Comparable): var celsius: Float64 def __lt__(self, other: Temperature) -> Bool: return self.celsius < other.celsius def __eq__(self, other: Temperature) -> Bool: return self.celsius == other.celsius def main() raises: var boiling = Temperature(100.0) var body = Temperature(37.0) print(boiling > body) # True

Mojo's Comparable makes the same bargain as Python's @total_ordering: implement __lt__ and __eq__, and the other four comparisons come with the trait.

Error Handling
Raising errors
def check_positive(number: int) -> int: if number <= 0: raise ValueError("Must be positive") return number print(check_positive(5))
def check_positive(number: Int) raises -> Int: if number <= 0: raise Error("Must be positive") return number def main() raises: print(check_positive(5))

Mojo's raise Error("message") is the equivalent of Python's raise ValueError("message"). Error carries a message string and is the everyday choice; a function may instead declare a type of its own — def check(value: Int) raises TooSmall — and raise values of that struct. The function signature must include raises to declare that errors may propagate from this function.

try / except
def risky_divide(dividend: float, divisor: float) -> float: if divisor == 0: raise ZeroDivisionError("Cannot divide by zero") return dividend / divisor try: print(risky_divide(10.0, 2.0)) print(risky_divide(5.0, 0.0)) except ZeroDivisionError as error: print("Caught:", error)
def risky_divide(dividend: Float64, divisor: Float64) raises -> Float64: if divisor == 0.0: raise Error("Cannot divide by zero") return dividend / divisor def main() raises: try: print(risky_divide(10.0, 2.0)) print(risky_divide(5.0, 0.0)) except error: print("Caught:", error)

Mojo uses the same try / except syntax as Python. The caught value in except error: has the type the called function raises — Error here. A function that declares its own error type, raises TooSmall, hands the except block a TooSmall, whose fields it can read.

Propagating errors
def read_config(path: str) -> str: if path != "app.yaml": raise FileNotFoundError(f"File not found: {path}") return "config content" def initialize_app() -> str: config = read_config("app.yaml") # exception propagates automatically return "App initialized with " + config try: print(initialize_app()) except FileNotFoundError as error: print("Setup failed:", error)
def read_config(path: String) raises -> String: if path != "app.yaml": raise Error("File not found: " + path) return "config content" def initialize_app() raises -> String: var config = read_config("app.yaml") # error propagates automatically return "App initialized with " + config def main() raises: try: print(initialize_app()) except error: print("Setup failed:", error)

When a raises function calls another raises function without a try block, the error propagates up the call stack automatically — exactly as Python exceptions propagate through uncaught call frames. Mojo's raises annotation on each function in the chain makes the propagation path compiler-verified: a function that calls a raises function must itself declare raises or wrap the call in try/except.

Errors in struct methods
class BankAccount: def __init__(self, balance: float): self.balance = balance def withdraw(self, amount: float) -> float: if amount > self.balance: raise ValueError("Insufficient funds") self.balance -= amount return self.balance account = BankAccount(100.0) print(account.withdraw(40.0))
struct BankAccount: var balance: Float64 def __init__(out self, balance: Float64): self.balance = balance def withdraw(mut self, amount: Float64) raises -> Float64: if amount > self.balance: raise Error("Insufficient funds") self.balance -= amount return self.balance def main() raises: var account = BankAccount(100.0) print(account.withdraw(40.0))

A Mojo struct method can combine multiple qualifiers: mut self means the method may mutate the struct, and raises means it may propagate an error. Python methods always have implicit mutation access and can raise anything — Mojo makes both properties explicit in the signature. A caller can see at a glance what a method does to state and whether it can fail.

finally
def process(data: str) -> str: if not data: raise ValueError("Empty data") return data.upper() try: result = process("hello") print("Success:", result) except ValueError as error: print("Error:", error) finally: print("Always runs")
def process(data: String) raises -> String: if data.byte_length() == 0: raise Error("Empty data") return data.upper() def main() raises: try: var result = process("hello") print("Success:", result) except error: print("Error:", error) finally: print("Always runs")

Mojo has finally and it runs on the same paths Python's does — body succeeded, exception raised and caught, or an early return. It also has with, driven by __enter__ and __exit__ exactly as Python's context managers are. Both were measured on Mojo 1.0; older Mojo material says neither exists and points you at destructors instead. Destructors are still there and still run when a value goes out of scope, which remains the compile-time analogue of a context manager — but they are no longer the only way to guarantee cleanup.

Performance
SIMD vectors
# Python has no built-in SIMD; this is equivalent serial computation prices = [10.0, 20.0, 30.0, 40.0] discounted = [price * 0.9 for price in prices] print(discounted)
def main() raises: var prices = SIMD[DType.float64, 4](10.0, 20.0, 30.0, 40.0) var discounted = prices * 0.9 print(discounted)

SIMD[DType, width] is a vector type that maps directly to CPU SIMD instructions. The operation prices * 0.9 applies the discount to all four elements in a single CPU instruction instead of a loop. The width must be a power of two. This is one of Mojo's headline features for ML developers: vectorized math written with familiar operators — no NumPy import required for simple cases, no gap between the code and the machine instructions.

SIMD arithmetic and reduction
# Python serial equivalent vector_a = [1.0, 2.0, 3.0, 4.0] vector_b = [5.0, 6.0, 7.0, 8.0] combined = [a + b for a, b in zip(vector_a, vector_b)] total = sum(combined) print(total) # 36.0
def main() raises: var vector_a = SIMD[DType.float64, 4](1.0, 2.0, 3.0, 4.0) var vector_b = SIMD[DType.float64, 4](5.0, 6.0, 7.0, 8.0) var combined = vector_a + vector_b var total = combined.reduce_add() print(total) # 36.0

SIMD types support all standard arithmetic operators element-wise: +, -, *, /. The .reduce_add() method sums all elements into a scalar, equivalent to Python's sum(). On modern hardware, the four-element addition is performed in a single CPU instruction. The Python equivalent requires a list comprehension and zip() — clear and readable, but many times slower for large numerical arrays.

Compile-time parameters
# Python determines sizes and types at runtime def sum_batch(values: list[float]) -> float: return sum(values) batch = [10.0, 20.0, 30.0, 40.0] print(sum_batch(batch)) # 100.0
comptime BATCH_SIZE: Int = 4 def sum_batch[batch_size: Int](values: SIMD[DType.float64, batch_size]) -> Float64: return values.reduce_add() def main() raises: var batch = SIMD[DType.float64, BATCH_SIZE](10.0, 20.0, 30.0, 40.0) print(sum_batch[BATCH_SIZE](batch)) # 100.0

Square-bracket parameters like [batch_size: Int] are resolved at compile time — the compiler generates specialized code for each value of batch_size. Python determines everything at runtime: the list length, the element type, the function dispatch. Mojo's compile-time parameters allow the compiler to emit SIMD instructions of exactly the right width with no runtime branching. comptime constants serve as named compile-time values usable as type parameters.

Python interoperability
import numpy as np array = np.array([1.0, 2.0, 3.0, 4.0]) print(array.mean()) # 2.5
from python import Python def main() raises: var numpy = Python.import_module("numpy") var array = numpy.array([1.0, 2.0, 3.0, 4.0]) print(array.mean()) # 2.5

Mojo can import and call any Python library directly via Python.import_module(). This requires a CPython installation to be available at runtime, which the servers that compile the Mojo here do not have — which is why there is nothing to run. In a full Mojo installation, this works seamlessly: you can mix Mojo's performance-critical code with the entire Python ecosystem — NumPy, pandas, scikit-learn, PyTorch — in a single file, gradually replacing hot paths with typed Mojo functions and SIMD operations.

Thank you — anything else?