How Languages Run Your Code
From source to execution — explore how different languages handle types, compilation, and runtime. Interactive playgrounds let you see the differences firsthand.
The Execution Spectrum
Every programming language transforms your source code into something the computer can execute — but they do it very differently. Understanding this spectrum helps you choose the right tool for each job.
Interpreted
Bytecode is a compact, intermediate representation. It's not human-readable like source code, but not machine code either. A Virtual Machine (VM) interprets it at runtime.
Transpiled
Transpilation converts one language to another at the same level. TypeScript adds types for developer tooling, then strips them away to produce standard JavaScript.
Compiled
Machine code is the native language of your CPU. The compiler does all the work upfront, producing a binary that runs at maximum speed without any intermediary.
Dynamic Typing: Python
Python is dynamically typed — variables can hold any type, and type errors are only discovered when code actually runs.
Key Insight
Python type hints (like : str) are documentation only. The interpreter ignores them completely. You can pass a number where a string is expected and Python won't complain until something actually breaks at runtime.
Variables and Types
Python variable types and operations. Notice how type() shows the type is determined at runtime.
# Python variables and types
name = "Alice"
score = 100
height = 1.75
is_active = True
print(f"Name: {name} (type: {type(name).__name__})")
print(f"Score: {score} (type: {type(score).__name__})")
print(f"Height: {height} (type: {type(height).__name__})")
print(f"Active: {is_active} (type: {type(is_active).__name__})")Name: Alice (type: str)
Score: 100 (type: int)
Height: 1.75 (type: float)
Active: True (type: bool)Python Bytecode
Use the dis module to see what Python compiles your code to. This bytecode runs on the Python VM.
import dis
def greet(name):
return f"Hello, {name}!"
# Show the bytecode
print("Function bytecode:")
dis.dis(greet)
# Call the function
print("\nOutput:")
print(greet("World"))Function bytecode:
4 0 RESUME 0
5 2 LOAD_CONST 1 ('Hello, ')
4 LOAD_FAST 0 (name)
6 FORMAT_VALUE 0
8 LOAD_CONST 2 ('!')
10 BUILD_STRING 3
12 RETURN_VALUE
Output:
Hello, World!📖 Reading Python Bytecode
When you see bytecode output like this, here's what each part means:
3 0 RESUME 0
↑ ↑ ↑
line# offset argument
4 2 LOAD_CONST 1 ('Hello, ')
4 LOAD_FAST 0 (name)
6 FORMAT_VALUE 0
8 BUILD_STRING 3
10 RETURN_VALUEThe Python VM is a stack machine — operations push and pop values from a stack. This is simpler than register-based machines but requires more instructions.
Types That Disappear: TypeScript
TypeScript adds static types to JavaScript — but they exist only for developer tooling. At runtime, it's just JavaScript.
Key Insight
TypeScript types are completely erased during transpilation. The browser never sees : string or : number. This means TypeScript provides compile-time safety while keeping full JavaScript compatibility.
Type Erasure in Action
Click "Transpile" to see how TypeScript types are removed. Notice how generics, interfaces, and type annotations all vanish.
// Interfaces are completely erased
interface User {
id: number;
name: string;
email: string;
}
// Type annotations disappear
function greet(user: User): string {
return `Hello, ${user.name}!`;
}
// Generics become plain functions
function identity<T>(value: T): T {
return value;
}
// Type assertions vanish
const count = "42" as unknown as number;
// Usage - types only exist at compile time
const user: User = { id: 1, name: "Alice", email: "[email protected]" };
console.log(greet(user));
console.log(identity<string>("TypeScript"));// Interfaces are completely erased
// Type annotations disappear
function greet(user) {
return `Hello, ${user.name}!`;
}
// Generics become plain functions
function identity(value) {
return value;
}
// Type assertions vanish
const count = "42";
// Usage - types only exist at compile time
const user = { id: 1, name: "Alice", email: "[email protected]" };
console.log(greet(user));
console.log(identity("TypeScript"));Static Compiled: Rust & Go
Compiled languages check types at compile time and produce machine code directly. No interpreter, no VM, no runtime type information.
Key Insight
In Rust and Go, types are checked before the binary exists. If there's a type error, you can't even create the executable. The compiler rejects invalid code entirely, making certain bugs impossible.
// Types are part of the compiled binary
fn main() {
// Type inference: Rust figures out types
let x = 42; // i32
let y = 3.14; // f64
let name = "Alice"; // &str
// Explicit types when needed
let count: u64 = 1_000_000;
// Type errors = compilation fails
// let bad: i32 = "hello"; // ❌ Won't compile!
// The Result type forces error handling
let parsed: Result<i32, _> = "42".parse();
match parsed {
Ok(n) => println!("Parsed: {}", n),
Err(e) => println!("Error: {}", e),
}
}package main
import "fmt"
func main() {
// Type inference with :=
x := 42 // int
y := 3.14 // float64
name := "Alice" // string
// Explicit types
var count uint64 = 1_000_000
// Type errors = compilation fails
// var bad int = "hello" // ❌ Won't compile!
// Multiple return values for errors
if parsed, err := strconv.Atoi("42"); err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Parsed:", parsed)
}
fmt.Println(x, y, name, count)
}Side-by-Side Comparison
See the same concepts implemented across different languages. Interactive playgrounds for Python and TypeScript — static examples for Rust and Go.
Type Annotations
How each language handles type information
def greet(name: str) -> str:
"""Types are hints only - not enforced!"""
return f"Hello, {name}!"
# Python runs this without complaint
print(greet("World"))
print(greet(42)) # Works! Types are just hintsHello, World!
Hello, 42!function greet(name: string): string {
return `Hello, ${name}!`;
}
// greet(42); // ❌ Compile error!
// This line won't even compile
console.log(greet("World"));function greet(name) {
return `Hello, ${name}!`;
}
// greet(42); // ❌ Compile error!
// This line won't even compile
console.log(greet("World"));
fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}
fn main() {
// greet(42); // ❌ Won't even compile!
// Types are part of the program
println!("{}", greet("World"));
}Variables & Types
How variables and types work across paradigms
# Dynamic typing - variable type can change
x = 42 # int
print(f"x = {x}, type: {type(x).__name__}")
x = "hello" # now it's a string!
print(f"x = {x}, type: {type(x).__name__}")
x = [1, 2, 3] # now it's a list!
print(f"x = {x}, type: {type(x).__name__}")x = 42, type: int
x = hello, type: str
x = [1, 2, 3], type: list// Static typing - type is fixed at declaration
let x: number = 42;
console.log(`x = ${x}, type: number`);
// x = "hello"; // ❌ Type error!
// TypeScript won't let you reassign to different type
// But 'any' escapes the type system:
let y: any = 42;
y = "hello"; // This works with 'any'
console.log(`y = ${y}, type: any (dangerous!)`);// Static typing - type is fixed at declaration
let x = 42;
console.log(`x = ${x}, type: number`);
// x = "hello"; // ❌ Type error!
// TypeScript won't let you reassign to different type
// But 'any' escapes the type system:
let y = 42;
y = "hello"; // This works with 'any'
console.log(`y = ${y}, type: any (dangerous!)`);
package main
import "fmt"
func main() {
// Static typing - type is permanent
var x int = 42
fmt.Printf("x = %d, type: int\n", x)
// x = "hello" // ❌ Won't compile!
// Type inference still picks one type
y := 42 // Go infers: int
fmt.Printf("y = %d, type: int (inferred)\n", y)
// y = "hello" // ❌ Still won't compile!
}From Source to Execution
What happens when you run code in each language
import dis
def add(a, b):
return a + b
# Show the bytecode Python generates
print("Python compiles to bytecode:")
dis.dis(add)
print("\nThen CPython VM executes it:")
print(f"add(2, 3) = {add(2, 3)}")Python compiles to bytecode:
4 0 RESUME 0
5 2 LOAD_FAST 0 (a)
4 LOAD_FAST 1 (b)
6 BINARY_OP 0 (+)
10 RETURN_VALUE
Then CPython VM executes it:
add(2, 3) = 5// TypeScript with types
function add(a: number, b: number): number {
return a + b;
}
// Generic function with type constraint
function identity<T>(value: T): T {
return value;
}
const result = add(2, 3);
const str = identity("hello");
console.log(result, str);// TypeScript with types
function add(a, b) {
return a + b;
}
// Generic function with type constraint
function identity(value) {
return value;
}
const result = add(2, 3);
const str = identity("hello");
console.log(result, str);
fn add(a: i32, b: i32) -> i32 {
a + b // No semicolon = return value
}
// Generic function (monomorphized at compile)
fn identity<T>(value: T) -> T {
value
}
fn main() {
let result = add(2, 3);
let s = identity("hello");
println!("{} {}", result, s);
// Types are gone, machine code remains
}Summary: Type Systems Compared
| Aspect | 🐍 Python | 📘 TypeScript | 🦀 Rust / 🐹 Go |
|---|---|---|---|
| When types checked | Runtime (or never) | Build time | Compile time |
| What runs | Bytecode on VM | JavaScript | Machine code |
| Types at runtime | Yes (reflection) | No (erased) | No (baked in) |
| Type errors caught | Late (when code runs) | Early (before deploy) | Earliest (no binary) |
| Variable types | Can change | Fixed (but JS dynamic) | Permanently fixed |
| Best for | Scripts, data science, prototypes | Web apps, large JS codebases | Systems, performance-critical |
How This Page Works
🐍 Python Playground
- • Powered by Pyodide — CPython compiled to WebAssembly
- • ~15MB download on first run
- • Full Python 3.11 with standard library
- • Runs entirely in your browser sandbox
📘 TypeScript Playground
- • Uses the official TypeScript compiler
- • Transpiles in-browser via
ts.transpileModule() - • Shows type erasure in real-time
- • No server required — all client-side
Mobile users see pre-computed output since WebAssembly can be resource-intensive on mobile devices.
Explore more interactive learning experiences: