Sitemap

Thinking in Python when You Come from JavaScript

and more about the engines. This writing is part of #AlfiInPyConID

Both are Interpreted, aren’t they?

Before we jump into the difference between those two, I would love to remind us about interpreted language. Both JavaScript and Python are interpreted because their compiled output must be executed by a runtime interpreter; PVM in Python, V8 in JavaScript, both are virtual machines that run at runtime. The compilation step is also automatic and hidden from developers, no need to manual build like in C or Java.

If we take a look to the old version of them, both began as purely interpreted scripting languages, which mean there’s no process of producing standalone binaries. Even modern engine in Python’s PyPy or JavaScript’s V8 JIT, not change the fact that execution starts with interpretation and that runtime environment remains dynamic.

Compilation Technique

Python executes code we write through four stages: lexing, parsing, compilation, and interpretation. Python will firstly breaks the source code into tokens, then builds an Abstract Syntax Tree (AST) to validate structure and detect syntax errors. Next, Python will compile the AST into bytecode, a platform-independent set of instructions that stored in __pycache__ as .pyc files. Remember that bytecode is not a machine code that our machine will understand to execute the program.

The final step is the interpretation. Python Virtual Machine (PVM) will read the bytecode that contains set of intstructions and execute it without converting into native CPU code or machine code. Since Python is dynamic typed language, decisions are made at runtime, this makes python a hybrid language, which does compilation to turn the source code into bytecode, then interprets the bytecode in order the program to run.

On the other hands, JavaScript in V8 uses more advanced pipeline. The source code still pass through tokenization and parsing like Python does, but it’s faster since it will be immediately starts executing while collecting type feedback. This early execution enables fast startup, which make it suitable for web apps that requires quick Time to Interactive (TTI).

When a function is frequently executed, TurboFan or Crankshaft (will decide on the go) — part of V8 engine, compiles bytecode into highly optimized machine code using the collected runtime type information. This machine code is then executed directly by the CPU, making JavaScript faster the longer it runs.

Syntax Concept

Python is that guy, and JavaScript is that girl.

Easier to Ask Forgiveness than Permission (EAFP) is a Python concept of how to form codeing style, do what we expect to work, and if an exception happens, catch and handle it appropriately. it’s like assuming that needed variables, files, and other properties exist, and catch any problems later.

# the EAFP way
person = {"name": "Alfi", "age": 20}

try:
email = person["email"]
except KeyError:
email = "no-email@example.com"
# the non-EAFP (LBYL) way
person = {"name": "Alfi", "age": 20}

if "email" in person:
email = person["email"]
else:
email = "no-email@example.com"

JavaScript usually conducts a check first, whether something will succeed, then only proceed if we know it will work.

// the LBYL way
const user = { name: 'Alfi', age: null };

function handleUser() {
if (user.age === null) {
console.log("Please provide your age");
} else {
user.age *= 2;
}
}
// the EAFP way
const user = { name: 'Alfi', age: null };

function handleUserEvent() {
try {
user.age *= 2;
} catch (error) {
console.log("Please provide your age");
}
}

When attributes exist, EAFP is about 85% faster than LBYL in Python, but when exceptions are actually thrown, LBYL becomes over 4 times faster.

Get Alysia Alfi’s stories in your inbox

Join Medium for free to get updates from this writer.

The main idea of why do they perform different style is Python Virtual Machine (PVM) is specifically optimized to handle exceptions efficiently and cheaply, making the EAFP performant. This somehow leads to cleaner and more concise code.

JavaScript born in performance-critical browsers, V8’s JIT works best with exception-free since throwing it frequently can cause deoptimization and break performance. JavaScript’s type coercion system also return undefined instead of throwing errors, that’s why apply LBYL aligns with the natural and faster approach.

Scope Mechanism

The most immediate difference to notice is that Python lacks block scope. In modern JavaScript, we rely on let and const to trap variables inside curly braces {}, meaning it dies when that block closes. In Python, variables defined inside blocks like if, for, or while will leak out into the surrounding function. This means a variable created inside a loop remains alive and accessible for the rest of the function, similar to the old var keyword in JavaScript.

Python follows the LEGB rule: it searches in order for Local (function), Enclosing (nested functions), Global (module), and Built-in scopes. While this is similar to the JavaScript scope chain, Python is strictly function-scoped. There is no hoisting in the same way JS does it, the interpreter looks for a name in the current function first and stops immediately once it finds a match.

In JavaScript, if we assign a value to a variable found in an upper scope, JS simply updates it. In Python, the moment we use the assignment operator (=), the interpreter assumes we are defining a new local variable, leaving the global variable untouched. This default behavior protects outside state from accidental side effects but can lead to confusing bugs where a variable seems to "reset" inside a function.

To actually modify a variable that lives outside the current scope, Python requires us to be explicit. We must use the global keyword to target module-level variables or nonlocal to target variables in an enclosing function. This aligns with Python’s philosophy that "explicit is better than implicit" by forcing us to declare our intent to change the outer state, the code becomes easier to read and debug, avoiding the silent mutation issues often found in complex JavaScript closures.

Error Detection

Both Python and JavaScript catch syntax errors before execution. Python detects syntax errors during compilation (to bytecode), while JavaScript catches errors during parsing to AST. For syntax error case like missing colon or parenthesis, the code never runs at all. Neither language can detect undefined variables at compile time because both are dynamically typed, which for that, variables are added by the ongoing runtime.

Python has a simple two phases of error model: compile time syntax error and runtime error. When a runtime error occurs, like NameError or TypeError, Python stops execution immediately at that line. This behavior is completely predictable, meaning that first run of the program behaves exactly like the 100th run. No performance degradation or optimization changes. It is what it is.

Javascript has a unique three phases of error model that includes deoptimization. Initially work the same as Python compiler; catches syntax errors during parsing and runtime errors during execution, the process afterward what makes it differ from Python, JavaScript’s V8 can deoptimize when JIT compiler fails. For example, if a function is called 10.000 times with numbers as param, then V8 already acknowledge that the functions has numbers as param, but suddenly receives string, V8 silently reverts from fast machine code back to slower bytecode interpretation. Meaning no error throw, just a performance drop.

When there’s a call to a function with mixed types (numbers & strings), python will throw a clear TypeError and stop execution when inconsistent operations occur. JavaScript performs type coercion and continue running-adding a number and string produces number mixes with string, with no error, though it may trigger deoptimization that silently degrades performance. See the example below:

# Python
def calculate(x, y):
return x + y

calculate(5, 10) # Works: 15
calculate(5, "b") # TypeError at runtime
// JavaScript
function calculate(x, y) {
return x + y;
}

calculate(5, 10); // Works: 15 (V8 optimizes for numbers)
calculate(5, "b"); // Works: "5b" (V8 deoptimizes! / coercion, no error)

That’s why the EAFP & LYBL style suit them both as the syntax itself is perfectly represent how the engine works.

Switching Languages

Python is consistent: predictable error behavior, explicit errors, straightforward stop execution. JavaScript needs more attention since that work-like-charm code might be running slowly due to deoptimization. Python pays the interpretation cost consistently, while JavaScript trades predictability for the potential of much higher performance.