NEOLearn

JavaScript and Python differences

Where JavaScript and Python behave differently in NEO scripts, and how to write a script that leaves the same result in both.

Updated October 17, 2026

On this page

JavaScript and Python scripts run on the same engine and see the same World API, but each language keeps its own rules. [] ? 1 : 2 is 1 in JavaScript; 1 if [] else 2 is 2 in Python. NEO doesn't change either language. What it guarantees is that values crossing into the world are the same, so a script written carefully in both languages leaves the same state and effects.

This matters when you port a script from one language to the other, or when you keep twins of a script, as in Paired examples.

The mapping table#

TopicJavaScriptPythonTo get the same result
Remainder-7 % 3 is -1 (sign of the dividend)-7 % 3 is 2 (sign of the divisor)Write the rule out (see below).
Integer divisionNo operator: Math.floor(-7 / 2) is -4-7 // 2 is -4Math.floor(a / b) matches a // b.
Division7 / 2 is 3.57 / 2 is 3.5Same.
Rounding halvesMath.round(2.5) is 3, Math.round(-2.5) is -2round(2.5) is 2, round(-2.5) is -2 (halves to even)Avoid rounding exact halves, or use Math.floor(x + 0.5) and math.floor(x + 0.5).
Number to textString(1.0) is "1"str(1.0) is "1.0"Format numbers yourself if text must match.
Large integers2 ** 53 + 1 is 9007199254740992 (rounded)2 ** 53 + 1 is 9007199254740993 (exact)Keep integers within ±2^53.
Divide by zero1 / 0 is Infinity1 / 0 raises ZeroDivisionErrorCheck the divisor first.
Truthiness[] and {} are true; 0, "", NaN, null, undefined are falseEmpty containers are false; 0, "", None are false; nan is trueCompare explicitly: list.length === 0 / len(xs) == 0.
String length"😀".length is 2 (UTF-16 units)len("😀") is 1 (code points)Use [...s].length in JavaScript.
String index"😀ab".indexOf("a") counts UTF-16 unitss.find("a") counts code pointsUse [...s].indexOf(...) in JavaScript.
Equality=== compares objects by identity== compares lists, dicts and tuples by valueCompare fields, not whole objects.
Map keysObject keys are strings; integer-like keys come first when listed1, 1.0 and True are the same dict keyUse string keys that aren't numbers.
Default sort[10, 9, 1].sort() gives [1, 10, 9] (sorts as text)sorted([10, 9, 1]) gives [1, 9, 10]Always pass a comparator in JavaScript: (a, b) => a - b.
Text operators"3" * "4" is 12"3" * "4" raises TypeErrorDon't rely on coercion.
JSON textJSON.stringify({ b: 1, 2: 0, a: 2 }) is {"2":0,"b":1,"a":2}json.dumps(...) keeps insertion order and adds spacesDon't compare JSON strings across languages.

Both languages agree on the things that come from the engine: the same random stream from this.rng.random(), the same maths functions, the same simulation clock, the same handler and timer order, and the same faults for running out of fuel or memory.

Writing integer maths portably#

JavaScript has no floor-division or floor-modulo operator. Define small helpers in your JavaScript script (they're plain code, not part of neo), and use Python's operators directly:

JavaScript
const floordiv = (a, b) => Math.floor(a / b);
const mod = (a, b) => ((a % b) + b) % b;

const pairs = [[7, 2], [-7, 2], [7, -2], [-7, -2], [0, 3]];
this.state.div = pairs.map(([a, b]) => floordiv(a, b)); // [3, -4, -4, 3, 0]
this.state.mod = pairs.map(([a, b]) => mod(a, b));      // [1, 1, -1, -1, 0]

Writing string code portably#

JavaScript
const s = "héllo 😀 wörld";
this.state.len = [...s].length;                // 13
this.state.upper = s.toUpperCase();
this.state.rev = [...s].reverse().join("");
this.state.find = [...s].indexOf("w");         // 8

Numbers are canonicalised at the boundary#

Every value that reaches the world (state, emitted payloads, method arguments, fields) is converted to one shared set of types, the same from both languages:

  • A whole number within ±2^53, except -0, is an integer. It doesn't matter whether it came from a JavaScript number or a Python float: 0.5 + 0.5 is stored as the integer 1 in both.
  • Everything else is a float: fractions, -0, infinities, NaN, and whole numbers beyond 2^53. Every NaN is stored the same way.
  • Python reads integers back as int. self.state.x = 1.0 reads back as 1. JavaScript can't tell the difference.
  • Integers beyond 64 bits are refused in both languages (a JavaScript BigInt or a Python int that's too large raises an error). A stored integer beyond 2^53 reaches JavaScript as a BigInt, never as a rounded number.
  • Maps have string keys only. A Python dict with other keys can't cross.
  • Strings are stored as text, so a string written by one language reads back identically in the other.

Names#

JavaScript uses camelCase and Python uses snake_case for the same state keys, fields and methods: this.state.lastOpener and self.state.last_opener are one key. The rule is in Script state.

See also#