Output & Running
Hello, World
print("Hello, World!") console.log("Hello, World!"); console.log() is the TypeScript/JavaScript equivalent of Python's print(). Both append a trailing newline. TypeScript files run via tsx execute as Node.js scripts — no browser, no index.html needed.Printing multiple values
name = "Alice"
age = 30
print(name, age) # space-separated
print(f"{name} is {age}") # f-string const name = "Alice";
const age = 30;
console.log(name, age); // space-separated
console.log(`${name} is ${age}`); // template literal TypeScript template literals use backticks and
${...} for interpolation — the direct equivalent of Python f-strings. console.log() with multiple arguments separates them with spaces, just like Python's print().Debug / inspect output
coordinates = (1, 2, 3)
numbers = [1, 2, 3]
print(repr(coordinates))
print(repr(numbers)) const coordinates = [1, 2, 3] as const;
const numbers = [1, 2, 3];
console.log(coordinates);
console.log(numbers); console.log() in Node.js prints a machine-readable representation of objects and arrays, similar to Python's repr(). For deeper inspection, console.dir(obj, { depth: null }) prints the full nested structure.Comments
# single-line comment
"""
Multi-line
docstring / comment
"""
x = 42 # inline comment // single-line comment
/*
Multi-line
comment
*/
const x = 42; // inline comment TypeScript uses C-style comments:
// for single-line and /* */ for multi-line. Python's triple-quoted strings are often used as multi-line comments, but TypeScript has dedicated block comment syntax.Variables & Types
const vs let vs var
name = "Alice" # always mutable
count = 0
count += 1
print(name, count) const name = "Alice"; // cannot be reassigned
let count = 0; // can be reassigned
count += 1;
console.log(name, count); const prevents reassignment (like a final binding); let allows it. In Python every name is always reassignable. Prefer const by default — use let only when you need to rebind. Avoid var: it has function scope instead of block scope and causes subtle bugs.Type inference
x = 42 # int inferred at runtime
y = 3.14 # float inferred at runtime
z = "hello" # str inferred at runtime
print(type(x), type(y), type(z)) const x = 42; // number — inferred at compile time
const y = 3.14; // number — inferred at compile time
const z = "hello"; // string — inferred at compile time
console.log(typeof x, typeof y, typeof z); TypeScript infers types at compile time from initial values — you rarely need explicit annotations for local variables. Unlike Python's runtime inference, TypeScript's is locked in at compile time. Note that TypeScript has a single
number type for both integers and floats.Explicit type annotations
score: int = 100
ratio: float = 0.75
name: str = "Alice"
print(score, ratio, name) const score: number = 100;
const ratio: number = 0.75;
const name: string = "Alice";
console.log(score, ratio, name); Python type hints use the same colon syntax, but they are checked only by external tools (mypy) and ignored at runtime. TypeScript type annotations are enforced by the compiler — a type mismatch is always a compile error, not a linting warning.
any and unknown
# Python: all variables are dynamically typed
value: object = 42 # accepts anything
print(value) let flexible: any = 42;
flexible = "now a string"; // no error — any opts out of type checking
console.log(flexible);
let safer: unknown = 42;
// console.log(safer.toFixed(2)); // compile error — must narrow first
if (typeof safer === "number") {
console.log(safer.toFixed(2)); // now safe
} any disables all type checking — it is an escape hatch that should be used sparingly. unknown is the safer alternative: it accepts any value but forces you to narrow the type before using it. Python's object type annotation is closest to unknown.null and undefined
result = None
if result is None:
print("nothing here")
else:
print(result) let result: string | null = null;
if (result === null) {
console.log("nothing here");
} else {
console.log(result);
} TypeScript has two "empty" values:
null (explicitly absent) and undefined (not yet assigned). Python has only None. With strictNullChecks enabled (the default in modern projects), nullable types must be explicitly declared as T | null — you cannot accidentally pass null where a string is expected.Union types
from typing import Union
def describe(value: Union[int, str]) -> str:
if isinstance(value, int):
return f"number: {value}"
return f"string: {value}"
print(describe(42))
print(describe("hello")) function describe(value: number | string): string {
if (typeof value === "number") {
return `number: ${value}`;
}
return `string: ${value}`;
}
console.log(describe(42));
console.log(describe("hello")); TypeScript union types (
A | B) map directly to Python's Union[A, B] (or A | B in Python 3.10+). TypeScript's type narrowing with typeof parallels Python's isinstance() check.Strings
Template literals (f-strings)
name = "Alice"
age = 30
greeting = f"Hello, {name}! You are {age} years old."
print(greeting) const name = "Alice";
const age = 30;
const greeting = `Hello, ${name}! You are ${age} years old.`;
console.log(greeting); TypeScript template literals use backtick delimiters and
${...} for expressions — the direct equivalent of Python f-strings. Any expression can go inside ${...}, including function calls and ternaries.Common string methods
text = " Hello, World! "
print(text.strip())
print(text.strip().upper())
print(text.strip().lower())
print(text.strip().replace("World", "TypeScript")) const text = " Hello, World! ";
console.log(text.trim());
console.log(text.trim().toUpperCase());
console.log(text.trim().toLowerCase());
console.log(text.trim().replace("World", "TypeScript")); TypeScript string methods mirror Python's:
trim() replaces strip(), toUpperCase() replaces upper(). JavaScript strings are primitives but auto-box to objects when you call methods — you don't need to import anything.Split and join
sentence = "one two three"
words = sentence.split()
print(words)
joined = ", ".join(words)
print(joined) const sentence = "one two three";
const words = sentence.split(" ");
console.log(words);
const joined = words.join(", ");
console.log(joined); split() in TypeScript requires an explicit separator — there is no whitespace-splitting shorthand like Python's bare split(). Use /\s+/ as a regex separator to split on any whitespace. join() is a method on arrays (not on the separator string as in Python).Contains / starts with / ends with
text = "Hello, World!"
print("World" in text)
print(text.startswith("Hello"))
print(text.endswith("!")) const text = "Hello, World!";
console.log(text.includes("World"));
console.log(text.startsWith("Hello"));
console.log(text.endsWith("!")); TypeScript uses method calls where Python uses the
in operator and methods. Note the camelCase: startsWith (not startswith), endsWith (not endswith). All three return booleans.Multi-line strings
message = """
Hello,
World!
""".strip()
print(message) const message = `
Hello,
World!
`.trim();
console.log(message); TypeScript template literals (backtick strings) are inherently multi-line, directly replacing Python's triple-quoted strings. The
trim() call removes the leading and trailing newlines introduced by the formatting.Numbers
The number type
x: int = 42
y: float = 3.14
print(type(x), type(y)) # <class 'int'>, <class 'float'> const x: number = 42;
const y: number = 3.14;
console.log(typeof x, typeof y); // "number" "number" TypeScript has a single
number type for all numeric values — there is no separate integer type. All numbers are 64-bit IEEE 754 floats under the hood, the same as Python's float. This means very large integers lose precision, unlike Python's arbitrary-precision int.Arithmetic operators
print(10 + 3) # 13
print(10 - 3) # 7
print(10 * 3) # 30
print(10 / 3) # 3.3333... (always float)
print(10 // 3) # 3 (integer division)
print(10 % 3) # 1
print(2 ** 10) # 1024 console.log(10 + 3); // 13
console.log(10 - 3); // 7
console.log(10 * 3); // 30
console.log(10 / 3); // 3.3333...
console.log(Math.floor(10 / 3)); // 3 (integer division)
console.log(10 % 3); // 1
console.log(2 ** 10); // 1024 TypeScript's
/ always returns a float, like Python's. There is no // integer division operator — use Math.floor(a / b) or Math.trunc(a / b). The ** exponentiation operator exists in both languages.NaN and Infinity
import math
print(math.isnan(float("nan")))
print(math.isinf(float("inf")))
try:
print(1 / 0)
except ZeroDivisionError as error:
print(f"ZeroDivisionError: {error}") console.log(Number.isNaN(NaN)); // true
console.log(Number.isFinite(Infinity)); // false
console.log(1 / 0); // Infinity — no error! TypeScript (like JavaScript) never throws a
ZeroDivisionError — dividing by zero produces Infinity, and 0 / 0 produces NaN. Python raises ZeroDivisionError for integer division by zero. Always use Number.isNaN(), not the global isNaN() which has surprising behavior.BigInt (arbitrary precision)
big = 10 ** 50
print(big) # Python int has no limit
print(type(big)) # <class 'int'> const big = 10n ** 50n;
console.log(big); // 100000...000
console.log(typeof big); // "bigint" TypeScript has a separate
bigint type for arbitrary-precision integers, written with an n suffix. Python's int is always arbitrary precision. You cannot mix number and bigint in arithmetic — explicit conversion is required.Collections
Arrays (list equivalent)
fruits = ["apple", "banana", "cherry"]
fruits.append("date")
print(fruits[0])
print(fruits[-1])
print(len(fruits)) const fruits: string[] = ["apple", "banana", "cherry"];
fruits.push("date");
console.log(fruits[0]);
console.log(fruits[fruits.length - 1]);
console.log(fruits.length); TypeScript arrays are typed:
string[] or equivalently Array<string>. There is no negative indexing — use fruits.at(-1) (ES2022+) for the last element. push() replaces append() and length replaces len().Array methods
numbers = [3, 1, 4, 1, 5, 9]
numbers.sort()
print(numbers)
print(numbers[1:4]) # slice
print(sum(numbers)) const numbers = [3, 1, 4, 1, 5, 9];
numbers.sort((a, b) => a - b);
console.log(numbers);
console.log(numbers.slice(1, 4)); // [1, 4, 5]
console.log(numbers.reduce((total, n) => total + n, 0)); TypeScript's
sort() requires a comparator for numbers — the default sorts lexicographically (so [10, 9].sort() gives [10, 9], not [9, 10]). slice(start, end) uses exclusive end like Python. There is no built-in sum() — use reduce().Objects (dict equivalent)
person = {"name": "Alice", "age": 30}
person["city"] = "NYC"
print(person["name"])
print(person.get("missing", "default"))
print(len(person)) const person: Record<string, unknown> = { name: "Alice", age: 30 };
person["city"] = "NYC";
console.log(person["name"]);
console.log(person["missing"] ?? "default");
console.log(Object.keys(person).length); JavaScript objects serve as Python's dict equivalent for string-keyed maps.
Record<string, unknown> is a typed open-ended map. The ?? nullish coalescing operator replaces Python's .get(key, default) pattern. For non-string keys, use Map<K, V> instead.Map (typed key-value store)
scores = {"Alice": 95, "Bob": 87, "Carol": 92}
scores["Dave"] = 88
print(scores.get("Alice"))
print(len(scores))
for name, score in scores.items():
print(f"{name}: {score}") const scores = new Map<string, number>([
["Alice", 95], ["Bob", 87], ["Carol", 92]
]);
scores.set("Dave", 88);
console.log(scores.get("Alice"));
console.log(scores.size);
for (const [name, score] of scores) {
console.log(`${name}: ${score}`);
} Map<K, V> preserves insertion order (like Python's dict since 3.7) and supports any key type. get() returns undefined (not an error) when the key is missing. Use size (not length) to count entries.Set
unique = {1, 2, 3, 2, 1}
unique.add(4)
print(3 in unique)
print(len(unique)) const unique = new Set([1, 2, 3, 2, 1]);
unique.add(4);
console.log(unique.has(3));
console.log(unique.size); TypeScript's
Set works like Python's set — unique values, O(1) lookup. Use has() instead of Python's in operator, and size instead of len(). TypeScript infers the element type from the initializer.Destructuring
point = (10, 20)
x, y = point # tuple unpacking
first, *rest = [1, 2, 3, 4]
print(x, y)
print(first, rest) const point = [10, 20] as const;
const [x, y] = point; // array destructuring
const [first, ...rest] = [1, 2, 3, 4];
console.log(x, y);
console.log(first, rest); TypeScript destructuring mirrors Python's tuple/list unpacking syntax. The rest element (
...rest) matches Python's *rest. Object destructuring is also available: const { name, age } = person — there is no Python equivalent (you must access each attribute explicitly).Spread operator
first = [1, 2, 3]
second = [4, 5, 6]
combined = first + second
print(combined)
defaults = {"color": "blue", "size": "medium"}
custom = {"color": "red"}
merged = {**defaults, **custom}
print(merged) const first = [1, 2, 3];
const second = [4, 5, 6];
const combined = [...first, ...second];
console.log(combined);
const defaults = { color: "blue", size: "medium" };
const custom = { color: "red" };
const merged = { ...defaults, ...custom };
console.log(merged); The TypeScript spread operator (
...) replaces Python's + for array concatenation and ** for dict merging. Later keys override earlier ones — the same behavior as Python's dict merge.Control Flow
if / else if / else
score = 85
if score >= 90:
print("A")
elif score >= 80:
print("B")
else:
print("C or below") const score = 85;
if (score >= 90) {
console.log("A");
} else if (score >= 80) {
console.log("B");
} else {
console.log("C or below");
} TypeScript uses
else if (not elif) and requires parentheses around conditions and curly braces around blocks. Indentation is not syntactically significant.Ternary / conditional expression
score = 85
result = "pass" if score >= 60 else "fail"
print(result) const score = 85;
const result = score >= 60 ? "pass" : "fail";
console.log(result); TypeScript uses the C-style ternary
condition ? trueValue : falseValue. Python's x if cond else y reads more naturally but means the same thing. Both are expressions and can be used inside template literals.Nullish coalescing (??) and optional chaining (?.)
user = {"name": "Alice"}
city = user.get("city", "Unknown")
print(city)
# optional chaining equivalent
address = user.get("address")
zip_code = address["zip"] if address else None
print(zip_code) const user: Record<string, unknown> = { name: "Alice" };
const city = (user["city"] as string) ?? "Unknown";
console.log(city);
const address = user["address"] as Record<string, string> | undefined;
const zipCode = address?.["zip"];
console.log(zipCode); ?? (nullish coalescing) returns the right side only when the left is null or undefined — unlike Python's or, which triggers on any falsy value (including 0 and ""). ?. (optional chaining) short-circuits to undefined instead of throwing when the object is null/undefined.switch statement
direction = "north"
match direction:
case "north": print("Going north")
case "south": print("Going south")
case _: print("Unknown") const direction = "north";
switch (direction) {
case "north":
console.log("Going north");
break;
case "south":
console.log("Going south");
break;
default:
console.log("Unknown");
} TypeScript's
switch requires break in each case to prevent fallthrough to the next case — omitting it is a common bug. Python's match never falls through. For exhaustive checks over union types, consider TypeScript's discriminated unions with switch and a default that asserts never.Loops & Iterators
for...of (iterate values)
fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
print(fruit) const fruits = ["apple", "banana", "cherry"];
for (const fruit of fruits) {
console.log(fruit);
} for...of iterates the values of an iterable, directly equivalent to Python's for x in iterable. Do not confuse it with for...in, which iterates keys (array indices as strings) and is rarely what you want.for over a range
for i in range(5):
print(i) for (let i = 0; i < 5; i++) {
console.log(i);
} TypeScript has no built-in
range() function. A classic C-style for loop is the standard replacement. Alternatively, Array.from({ length: 5 }, (_, i) => i) creates the array [0,1,2,3,4] for use with for...of.Enumerate (index + value)
colors = ["red", "green", "blue"]
for index, color in enumerate(colors):
print(f"{index}: {color}") const colors = ["red", "green", "blue"];
for (const [index, color] of colors.entries()) {
console.log(`${index}: ${color}`);
} Array.entries() returns an iterator of [index, value] pairs, equivalent to Python's enumerate(). Destructuring in the for...of head extracts both values cleanly.map / filter / reduce
numbers = [1, 2, 3, 4, 5]
doubled = list(map(lambda n: n * 2, numbers))
evens = list(filter(lambda n: n % 2 == 0, numbers))
total = sum(numbers)
print(doubled, evens, total) const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2);
const evens = numbers.filter(n => n % 2 === 0);
const total = numbers.reduce((accumulator, n) => accumulator + n, 0);
console.log(doubled, evens, total); TypeScript's
map(), filter(), and reduce() are array methods — they return new arrays immediately (eager), unlike Python's map() and filter() which return lazy iterators. Arrow functions (n => n * 2) replace Python lambdas.List comprehensions
numbers = [1, 2, 3, 4, 5]
squares = [n * n for n in numbers]
even_squares = [n * n for n in numbers if n % 2 == 0]
print(squares)
print(even_squares) const numbers = [1, 2, 3, 4, 5];
const squares = numbers.map(n => n * n);
const evenSquares = numbers.filter(n => n % 2 === 0).map(n => n * n);
console.log(squares);
console.log(evenSquares); TypeScript has no list comprehension syntax. Chain
.filter() and .map() to express the same logic. This is more verbose but equally readable. A future proposal for JavaScript "iterator helpers" may eventually bring comprehension-like syntax.Functions
Function definition
def greet(name: str) -> str:
return f"Hello, {name}!"
print(greet("Alice")) function greet(name: string): string {
return `Hello, ${name}!`;
}
console.log(greet("Alice")); TypeScript function signatures require type annotations on parameters and the return type — unlike Python where annotations are optional. The syntax
function name(param: Type): ReturnType { } maps directly to Python's def name(param: Type) -> ReturnType:.Arrow functions (lambdas)
double = lambda n: n * 2
add = lambda x, y: x + y
print(double(5))
print(add(3, 4)) const double = (n: number): number => n * 2;
const add = (x: number, y: number): number => x + y;
console.log(double(5));
console.log(add(3, 4)); Arrow functions (
(params) => expr) are TypeScript's equivalent of Python lambdas, but they can span multiple lines with a block body: (x) => { return x * 2; }. Unlike Python lambdas (limited to one expression), arrow functions can contain full statement blocks.Default parameters
def greet(name: str, greeting: str = "Hello") -> str:
return f"{greeting}, {name}!"
print(greet("Alice"))
print(greet("Bob", "Hi")) function greet(name: string, greeting: string = "Hello"): string {
return `${greeting}, ${name}!`;
}
console.log(greet("Alice"));
console.log(greet("Bob", "Hi")); Default parameters work identically in both languages — the syntax is the same. TypeScript (unlike Python) does not have keyword-only arguments, but you can simulate them by accepting an object:
greet({ name: "Alice", greeting: "Hi" }).Rest parameters (*args)
def sum_all(*numbers: int) -> int:
return sum(numbers)
print(sum_all(1, 2, 3))
print(sum_all(10, 20, 30, 40)) function sumAll(...numbers: number[]): number {
return numbers.reduce((total, n) => total + n, 0);
}
console.log(sumAll(1, 2, 3));
console.log(sumAll(10, 20, 30, 40)); Rest parameters in TypeScript use the
...name: Type[] syntax — the direct equivalent of Python's *args. Inside the function, numbers is a regular typed array. TypeScript has no **kwargs equivalent; use an object parameter instead.Optional parameters
from typing import Optional
def find(haystack: list, needle: str,
start: Optional[int] = None) -> int:
if start is None:
return haystack.index(needle)
return haystack.index(needle, start)
print(find(["a", "b", "c"], "b")) function find(haystack: string[], needle: string,
start?: number): number {
return haystack.indexOf(needle, start);
}
console.log(find(["a", "b", "c"], "b")); A parameter marked
? is optional — callers may omit it, and its type is automatically widened to T | undefined. This replaces the Python pattern of param: Optional[T] = None. Optional parameters must come after required ones.Classes & OOP
Basic class
class Animal:
def __init__(self, name: str, sound: str) -> None:
self.name = name
self.sound = sound
def speak(self) -> str:
return f"{self.name} says {self.sound}"
cat = Animal("Cat", "meow")
print(cat.speak()) class Animal {
name: string;
sound: string;
constructor(name: string, sound: string) {
this.name = name;
this.sound = sound;
}
speak(): string {
return `${this.name} says ${this.sound}`;
}
}
const cat = new Animal("Cat", "meow");
console.log(cat.speak()); TypeScript class fields must be declared before the constructor (Python infers them from assignments in
__init__). this replaces Python's self, and constructor replaces __init__. Instantiation uses new.Access modifiers
class BankAccount:
def __init__(self, balance: float) -> None:
self._balance = balance # convention: "private"
def deposit(self, amount: float) -> None:
self._balance += amount
def get_balance(self) -> float:
return self._balance
account = BankAccount(100.0)
account.deposit(50.0)
print(account.get_balance()) class BankAccount {
private balance: number;
constructor(balance: number) {
this.balance = balance;
}
deposit(amount: number): void {
this.balance += amount;
}
getBalance(): number {
return this.balance;
}
}
const account = new BankAccount(100);
account.deposit(50);
console.log(account.getBalance()); TypeScript enforces
private, protected, and public at compile time — accessing a private field outside the class is a compile error. Python's single-underscore convention is just a social contract, not enforced by the interpreter.Inheritance
class Shape:
def area(self) -> float:
return 0.0
class Circle(Shape):
def __init__(self, radius: float) -> None:
self.radius = radius
def area(self) -> float:
import math
return math.pi * self.radius ** 2
circle = Circle(5.0)
print(circle.area()) class Shape {
area(): number {
return 0;
}
}
class Circle extends Shape {
constructor(private radius: number) {
super();
}
area(): number {
return Math.PI * this.radius ** 2;
}
}
const circle = new Circle(5);
console.log(circle.area()); TypeScript uses
extends for class inheritance; Python uses parentheses. super() must be called before accessing this in a subclass constructor. The constructor shorthand constructor(private radius: number) declares and assigns the field simultaneously.Interfaces & Type Aliases
Interface
from typing import Protocol
class Serializable(Protocol):
def to_json(self) -> str: ...
class User:
def __init__(self, name: str) -> None:
self.name = name
def to_json(self) -> str:
return f'{{"name": "{self.name}"}}'
def save(item: Serializable) -> None:
print(item.to_json())
save(User("Alice")) interface Serializable {
toJson(): string;
}
class User implements Serializable {
constructor(private name: string) {}
toJson(): string {
return JSON.stringify({ name: this.name });
}
}
function save(item: Serializable): void {
console.log(item.toJson());
}
save(new User("Alice")); TypeScript interfaces describe shape, not ancestry — an object satisfies an interface if it has the required methods and properties, even without an explicit
implements declaration (structural typing). Python's Protocol works the same way at static-analysis time.Type aliases
from typing import TypeAlias, Union
Coordinate = tuple[float, float]
NumberOrString: TypeAlias = Union[int, str]
point: Coordinate = (1.0, 2.0)
print(point) type Coordinate = [number, number];
type NumberOrString = number | string;
const point: Coordinate = [1.0, 2.0];
console.log(point); TypeScript's
type keyword creates an alias for any type expression. Python's TypeAlias (PEP 613) is the equivalent. Unlike interfaces, type aliases can represent unions, intersections, tuples, and primitive types — not just object shapes.Type narrowing
from typing import Union
def process(value: Union[int, str]) -> None:
if isinstance(value, int):
print(f"Integer: {value * 2}")
elif isinstance(value, str):
print(f"String: {value.upper()}")
process(42)
process("hello") function process(value: number | string): void {
if (typeof value === "number") {
console.log(`Integer: ${value * 2}`);
} else if (typeof value === "string") {
console.log(`String: ${value.toUpperCase()}`);
}
}
process(42);
process("hello"); TypeScript narrows union types based on runtime checks: after
typeof value === "number", the compiler knows value is a number and allows numeric methods. This is the static-typing equivalent of Python's isinstance() guard.Utility types: Readonly and Partial
from dataclasses import dataclass
from typing import Optional
@dataclass(frozen=True)
class Config:
host: str
port: int
# Python has no built-in "all fields optional" utility
print(Config(host="localhost", port=8080)) interface Config {
host: string;
port: number;
}
const config: Readonly<Config> = { host: "localhost", port: 8080 };
// config.port = 9090; // compile error — Readonly prevents mutation
function update(current: Config, patch: Partial<Config>): Config {
return { ...current, ...patch };
}
console.log(update(config, { port: 9090 })); Readonly<T> makes all properties immutable (like Python's frozen=True dataclass). Partial<T> makes all properties optional — useful for update/patch functions where you only want to specify changed fields. These are called utility types and there are many more: Pick, Omit, Required, Record.Generics
Generic functions
from typing import TypeVar
T = TypeVar("T")
def first(items: list[T]) -> T:
return items[0]
print(first([1, 2, 3]))
print(first(["a", "b", "c"])) function first<T>(items: T[]): T {
return items[0];
}
console.log(first([1, 2, 3]));
console.log(first(["a", "b", "c"])); TypeScript generics use angle-bracket syntax
<T> on the function declaration; Python uses TypeVar. TypeScript infers the type parameter from the argument — you rarely need to write first<number>([1, 2, 3]) explicitly.Constrained generics
from typing import TypeVar
Comparable = TypeVar("Comparable", int, float, str)
def maximum(x: Comparable, y: Comparable) -> Comparable:
return x if x > y else y
print(maximum(3, 7))
print(maximum("apple", "banana")) function maximum<T extends number | string>(x: T, y: T): T {
return x > y ? x : y;
}
console.log(maximum(3, 7));
console.log(maximum("apple", "banana")); The
extends keyword constrains a type parameter: T extends number | string means "any type that is a subtype of number or string." Python's constrained TypeVar is the equivalent. Constraints allow calling methods known to exist on the constrained type.Generic interfaces
from typing import Generic, TypeVar
T = TypeVar("T")
class Box(Generic[T]):
def __init__(self, value: T) -> None:
self.value = value
def unwrap(self) -> T:
return self.value
box = Box(42)
print(box.unwrap()) interface Box<T> {
value: T;
unwrap(): T;
}
class NumberBox implements Box<number> {
constructor(public value: number) {}
unwrap(): number { return this.value; }
}
const box = new NumberBox(42);
console.log(box.unwrap()); TypeScript generic interfaces parallel Python's
Generic[T] class pattern. Once you specify the type parameter (Box<number>), the compiler enforces type consistency throughout — the same guarantee Python's Generic gives mypy.Error Handling
try / catch / finally
try:
result = int("not a number")
except ValueError as error:
print(f"Caught: {error}")
finally:
print("always runs") try {
const result = parseInt("not a number", 10);
if (Number.isNaN(result)) throw new Error("invalid number");
console.log(result);
} catch (error) {
console.log(`Caught: ${(error as Error).message}`);
} finally {
console.log("always runs");
} TypeScript's
try/catch/finally mirrors Python's. Unlike Python, the caught error is typed as unknown — you must narrow it (e.g., error instanceof Error) before accessing properties. This is intentional: TypeScript cannot know what type was thrown.Custom error classes
class ValidationError(ValueError):
def __init__(self, field: str, message: str) -> None:
self.field = field
super().__init__(f"{field}: {message}")
try:
raise ValidationError("email", "invalid format")
except ValidationError as error:
print(f"Validation failed on {error.field}: {error}") class ValidationError extends Error {
constructor(public field: string, message: string) {
super(`${field}: ${message}`);
this.name = "ValidationError";
}
}
try {
throw new ValidationError("email", "invalid format");
} catch (error) {
if (error instanceof ValidationError) {
console.log(`Validation failed on ${error.field}: ${error.message}`);
}
} Custom error classes extend
Error in TypeScript, just as they extend a built-in exception in Python. Always set this.name in the constructor — otherwise error.name shows "Error" instead of your class name. The instanceof check in the catch block narrows the type.Async & Promises
async / await
import asyncio
async def fetch_data(url: str) -> str:
await asyncio.sleep(0) # simulate I/O
return f"data from {url}"
async def main() -> None:
result = await fetch_data("https://example.com")
print(result)
asyncio.run(main()) async function fetchData(url: string): Promise<string> {
return `data from ${url}`;
}
async function main(): Promise<void> {
const result = await fetchData("https://example.com");
console.log(result);
}
main(); async/await works identically in both languages: async functions return a promise/coroutine, and await suspends until it resolves. TypeScript's return type is Promise<T> where Python's is implicit coroutine. TypeScript runs in a Node.js event loop, not an explicit asyncio.run().Running tasks concurrently
import asyncio
async def task(name: str, duration: float) -> str:
await asyncio.sleep(duration)
return f"{name} done"
async def main() -> None:
results = await asyncio.gather(
task("A", 0.1),
task("B", 0.2),
)
print(results)
asyncio.run(main()) async function task(name: string): Promise<string> {
return `${name} done`;
}
async function main(): Promise<void> {
const results = await Promise.all([
task("A"),
task("B"),
]);
console.log(results);
}
main(); Promise.all() runs multiple async operations concurrently and resolves when all complete, directly equivalent to Python's asyncio.gather(). If any promise rejects, Promise.all() rejects immediately — use Promise.allSettled() to wait for all results regardless of failure.Modules
import / export
# math_utils.py
# def add(x, y): return x + y
# def PI = 3.14159
# main.py
# from math_utils import add, PI
# import math_utils as mu // math_utils.ts
// export function add(x: number, y: number): number { return x + y; }
// export const PI = 3.14159;
// main.ts
// import { add, PI } from "./math_utils";
// import * as mu from "./math_utils"; TypeScript ES module syntax mirrors Python's import system closely: named exports (
export function) map to module-level names; import { name } is like Python's from module import name; and import * as alias is like Python's import module as alias.Default exports
# Python has no "default export" concept —
# every module-level name is equally importable // calculator.ts
// export default class Calculator {
// add(x: number, y: number): number { return x + y; }
// }
// main.ts
// import Calculator from "./calculator"; // any name works
// const calc = new Calculator(); TypeScript allows one
export default per file, which can be imported under any name. Python has no equivalent — all module-level names are importable by their original name. Default exports are useful for the primary export of a module but can make code harder to search.Type-only imports
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from mymodule import MyClass # imported only for type checking // import type { MyClass } from "./mymodule";
// This import is erased at compile time — zero runtime cost.
// Use it for types, interfaces, and type aliases that are
// only needed for annotation purposes. TypeScript's
import type is erased entirely at compile time — it adds no runtime module load. Python's equivalent is TYPE_CHECKING guard (a convention supported by mypy). This matters for avoiding circular dependencies and reducing bundle size.