Hello World & Running
Hello, World
console.log("Hello, World!"); print("Hello, World!") Python uses
print instead of console.log, with no semicolons and no boilerplate. Like a TypeScript script run through tsx, top-level code executes top to bottom the moment the file runs.f-strings
const name = "Ada";
console.log(`Hello, ${name}!`); name = "Ada"
print(f"Hello, {name}!") Python interpolates with an
f-prefixed string and single braces {name} — no backtick and no $. The expression inside the braces is evaluated, just like a TypeScript template literal.Indentation, not braces
if (true) {
console.log("a");
console.log("b");
} if True:
print("a")
print("b") Python has no curly braces and no semicolons — a colon opens a block and indentation (four spaces by convention) defines its body. Whitespace is syntactically significant, where in TypeScript it is purely cosmetic.
Variables & Types
Declaring variables
let count = 3;
const label = "items";
console.log(count, label); count = 3
label = "items"
print(count, label) Python has no
let, const, or var — a bare assignment creates the variable. There is no compile step and no enforced immutability; an ALL_CAPS name is the convention for "treat as constant," not a keyword the language checks.None vs null/undefined
const value: string | null = null;
console.log(value ?? "fallback"); value = None
print(value if value is not None else "fallback") Python's single absent value is
None — there is no undefined. Identity is checked with is rather than ===, and there is no ?? operator, so a conditional expression (Python's ternary) handles the fallback.Truthiness
const items: number[] = [];
if (items.length === 0) console.log("empty"); items = []
if not items:
print("empty") Empty collections,
0, "", and None are all falsy in Python, so if not items tests for an empty list directly. This is more aggressive than TypeScript, where an empty array is truthy.Strings
String operations
const text = "Hello, World";
console.log(text.toUpperCase());
console.log(text.includes("World"));
console.log(text.length); text = "Hello, World"
print(text.upper())
print("World" in text)
print(len(text)) Method names are
snake_case (upper, not toUpperCase). Substring testing uses the in operator rather than a method, and length comes from the standalone len() function rather than a .length property.Slicing
const text = "hello";
console.log(text.slice(1, 3));
console.log(text.at(-1)); text = "hello"
print(text[1:3])
print(text[-1]) Python's slice syntax
text[1:3] is built into the subscript operator and works on strings, lists, and tuples alike. Negative indices count from the end, so text[-1] is the last character — no .slice or .at method needed.Numbers
int vs float
console.log(7 / 2);
console.log(Math.floor(7 / 2));
console.log(7 % 2); print(7 / 2)
print(7 // 2)
print(7 % 2) Python has distinct
int and float types, unlike TypeScript's single number. The / operator always produces a float, while // is floor division returning an int — so 7 / 2 is 3.5 but 7 // 2 is 3.Arbitrary precision
const big = 2n ** 100n;
console.log(big.toString()); big = 2 ** 100
print(big) Python
int grows to arbitrary precision automatically — there is no separate BigInt type and no n suffix. 2 ** 100 just works and stays an ordinary int.Lists & Tuples
Lists
const numbers: number[] = [1, 2, 3];
numbers.push(4);
console.log(numbers[0], numbers.length); numbers = [1, 2, 3]
numbers.append(4)
print(numbers[0], len(numbers)) A Python
list is the everyday growable sequence. You append rather than push, and length is len(numbers). Lists are untyped and can hold mixed values.Tuples & unpacking
const point: [number, number] = [3, 4];
const [x, y] = point;
console.log(x, y); point = (3, 4)
x, y = point
print(x, y) A
tuple is an immutable fixed-length sequence written with parentheses — the runtime counterpart of TypeScript's tuple type. Destructuring is called "unpacking" and needs no brackets: x, y = point.Dicts & Sets
Dicts
const person: Record<string, unknown> = { name: "Ada", age: 36 };
console.log(person.name);
console.log(person["age"]); person = {"name": "Ada", "age": 36}
print(person["name"])
print(person.get("age")) A Python
dict looks like an object literal but is purely a key/value map, not a struct — there is no dot access, so you index with person["name"]. The .get() method returns None (or a supplied default) instead of raising on a missing key.Sets
const seen = new Set<number>();
seen.add(1);
seen.add(1);
console.log(seen.size);
console.log(seen.has(1)); seen = set()
seen.add(1)
seen.add(1)
print(len(seen))
print(1 in seen) Python's
set is a built-in literal type ({1, 2, 3}) with the in operator for membership and len() for size — more first-class than the JavaScript Set, and it supports union (|) and intersection (&) operators directly.Comprehensions
List comprehensions
const numbers = [1, 2, 3, 4, 5, 6];
const result = numbers
.filter(n => n % 2 === 0)
.map(n => n * 10);
console.log(result); numbers = [1, 2, 3, 4, 5, 6]
result = [n * 10 for n in numbers if n % 2 == 0]
print(result) A list comprehension folds
map and filter into one expression read as "this for each item if condition." It is the idiomatic Python alternative to chaining .filter().map(), and dict and set comprehensions follow the same shape.Dict comprehensions
const words = ["hi", "there"];
const lengths = Object.fromEntries(words.map(w => [w, w.length]));
console.log(lengths); words = ["hi", "there"]
lengths = {word: len(word) for word in words}
print(lengths) A dict comprehension builds a dictionary directly —
{key: value for ...} — replacing the Object.fromEntries(map(...)) dance. The same bracket style distinguishes it: [] for a list, {} for a dict or set.Control Flow
for and range
for (let index = 0; index < 3; index++) {
console.log(index);
} for index in range(3):
print(index) Python has no C-style counting loop. You iterate over
range(3) (which yields 0, 1, 2) or directly over any iterable with for x in items. There is no while (init; cond; post) form at all.enumerate & zip
const items = ["a", "b", "c"];
items.forEach((value, index) => {
console.log(index, value);
}); items = ["a", "b", "c"]
for index, value in enumerate(items):
print(index, value) When you need the index,
enumerate pairs it with each value — cleaner than the forEach((value, index)) callback. Its companion zip walks several sequences in lockstep, a common idiom with no terse TypeScript equivalent.Functions
Defining functions
function add(a: number, b: number): number {
return a + b;
}
console.log(add(2, 3)); def add(a, b):
return a + b
print(add(2, 3)) Functions use
def and a colon, with the body indented. Type annotations are optional and never enforced at runtime — they are hints for tools like mypy, not the compile-time guarantee TypeScript provides.Keyword & default arguments
function greet(name: string, greeting = "Hello"): string {
return `${greeting}, ${name}`;
}
console.log(greet("Ada")); def greet(name, greeting="Hello"):
return f"{greeting}, {name}"
print(greet("Ada")) Python has real keyword arguments with defaults: callers can write
greet("Ada", greeting="Hi") and pass them by name in any order — the language feature TypeScript imitates with an options object.Lambdas
const numbers = [3, 1, 2];
const sorted = [...numbers].sort((a, b) => b - a);
console.log(sorted); numbers = [3, 1, 2]
result = sorted(numbers, key=lambda n: -n)
print(result) A Python
lambda is a single-expression anonymous function — deliberately limited, since multi-line logic is expected to use def. The built-in sorted takes a key function rather than a comparator, computing one sort value per element.Classes
Classes
class Point {
constructor(public x: number, public y: number) {}
distance(): number { return Math.hypot(this.x, this.y); }
}
console.log(new Point(3, 4).distance()); import math
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def distance(self):
return math.hypot(self.x, self.y)
print(Point(3, 4).distance()) The constructor is
__init__ and every method takes an explicit self first parameter — Python never hides the receiver the way this does. You instantiate with Point(3, 4), with no new keyword.Inheritance
class Animal {
speak(): string { return "..."; }
}
class Dog extends Animal {
override speak(): string { return "Woof"; }
}
console.log(new Dog().speak()); class Animal:
def speak(self):
return "..."
class Dog(Animal):
def speak(self):
return "Woof"
print(Dog().speak()) A subclass lists its parent in parentheses —
class Dog(Animal) — instead of extends. Overriding needs no keyword; a same-named method replaces the parent's, and super().speak() calls up the chain.Dataclasses
Dataclasses
interface Point { x: number; y: number; }
const a: Point = { x: 1, y: 2 };
const b: Point = { x: 1, y: 2 };
console.log(a.x === b.x && a.y === b.y); from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
a = Point(1, 2)
b = Point(1, 2)
print(a == b)
print(a) The
@dataclass decorator generates the constructor, a readable repr, and value-based == from the annotated fields — so two points with equal fields compare equal, unlike a plain object. It is Python's closest analogue to a record type.Type Hints
Type hints
function repeat(text: string, times: number): string {
return text.repeat(times);
}
console.log(repeat("ab", 3)); def repeat(text: str, times: int) -> str:
return text * times
print(repeat("ab", 3)) Python annotations mirror TypeScript syntax —
name: type and -> return — but they are not enforced. The interpreter ignores them at runtime; a separate checker like mypy or pyright validates them, so types are advisory rather than a barrier to running.Optional & unions
function find(values: number[], target: number): number | null {
const index = values.indexOf(target);
return index >= 0 ? index : null;
}
console.log(find([1, 2, 3], 9)); def find(values: list[int], target: int) -> int | None:
return values.index(target) if target in values else None
print(find([1, 2, 3], 9)) Python 3.10+ writes union types with
| just like TypeScript, so int | None is the equivalent of number | null. Built-in generics use lowercase subscripts — list[int], dict[str, int] — rather than Array<number>.Pattern Matching
match statements
function describe(n: number): string {
switch (true) {
case n === 0: return "zero";
case n > 0: return "positive";
default: return "negative";
}
}
console.log(describe(-5)); def describe(n):
match n:
case 0:
return "zero"
case _ if n > 0:
return "positive"
case _:
return "negative"
print(describe(-5)) Python 3.10's
match is structural, not a C-style switch: cases can match literals, bind names, and add an if guard, with _ as the wildcard. There is no fall-through and no break.Structural matching
const data = { type: "point", x: 1, y: 2 };
if (data.type === "point") {
console.log(`(${data.x}, ${data.y})`);
} data = {"type": "point", "x": 1, "y": 2}
match data:
case {"type": "point", "x": x, "y": y}:
print(f"({x}, {y})") A
match can destructure dicts and sequences, binding x and y while requiring "type" to equal "point" — destructuring and a discriminant check in one step, more powerful than narrowing a TypeScript discriminated union by hand.Error Handling
try/except/finally
try {
throw new Error("boom");
} catch (error) {
console.log((error as Error).message);
} finally {
console.log("cleanup");
} try:
raise Exception("boom")
except Exception as error:
print(error)
finally:
print("cleanup") Python spells the keywords
try/except/finally and raises with raise. The caught exception is a typed object, and you name the class after except to catch only that kind — no as Error cast or unknown to narrow.Custom exceptions
class ValidationError extends Error {}
try {
throw new ValidationError("invalid");
} catch (error) {
if (error instanceof ValidationError) console.log("caught");
} class ValidationError(Exception):
pass
try:
raise ValidationError("invalid")
except ValidationError:
print("caught") A custom exception subclasses
Exception, and except ValidationError catches only that type — the equivalent of an instanceof guard, but selected by the except clause itself rather than checked inside the handler.Async
async/await
async function compute(): Promise<number> {
return 42;
}
(async () => {
console.log(await compute());
})(); import asyncio
async def compute():
return 42
print(asyncio.run(compute())) Python uses the same
async/await keywords, but a coroutine does nothing until driven by an event loop — asyncio.run starts one and waits for the result. There is no implicit top-level loop the way a JavaScript runtime always has one running.