Operators
Arithmetic, comparison, logical, string concatenation, and assignment operators in SFMC SSJS.
Arithmetic
var a = 10, b = 3;
a + b; // 13
a - b; // 7
a * b; // 30
a / b; // 3.333...
a % b; // 1 (modulo)
// Increment / decrement
var n = 0;
n++; // post-increment
++n; // pre-increment
n--; // post-decrement
--n; // pre-decrement
Show test script
<script runat="server">
/*
* Chapter: Arithmetic
* Proves:
* 1. + - * / % and ++/--.
* EXPECTED OUTPUT: every line starts with PASS.
*/
function assert(id, actual, expected) {
Platform.Response.Write((actual === expected ? "PASS " : "FAIL ") + id + " -> [" + actual + "]\n");
}
function assertThrows(id, fn) {
var threw = false, msg = "";
try { fn(); } catch (ex) { threw = true; msg = "" + ex.message; }
Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
var a = 10, b = 3;
assert("add", a + b, 13);
assert("sub", a - b, 7);
assert("mul", a * b, 30);
assert("div floor*1000", Math.floor((a / b) * 1000), 3333);
assert("mod", a % b, 1);
var n = 0; n++; assert("inc", n, 1); ++n; assert("preinc", n, 2); n--; assert("dec", n, 1); --n; assert("predec", n, 0);
</script>
String Concatenation
+ concatenates strings. When mixing types, JavaScript coerces non-strings:
"Hello, " + "World" // "Hello, World"
"Count: " + 42 // "Count: 42"
"Sum: " + (1 + 2) // "Sum: 3" (parentheses force arithmetic first)
"Sum: " + 1 + 2 // "Sum: 12" (left-to-right string concat!)
Concatenating across multiple lines:
var html = '<div class="profile">' +
'<h2>' + name + '</h2>' +
'<p>' + email + '</p>' +
'</div>';
Show test script
<script runat="server">
/*
* Chapter: String Concatenation
* Proves:
* 1. Documented concat coercion.
* EXPECTED OUTPUT: every line starts with PASS.
*/
function assert(id, actual, expected) {
Platform.Response.Write((actual === expected ? "PASS " : "FAIL ") + id + " -> [" + actual + "]\n");
}
function assertThrows(id, fn) {
var threw = false, msg = "";
try { fn(); } catch (ex) { threw = true; msg = "" + ex.message; }
Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
assert("hw", "Hello, " + "World", "Hello, World");
assert("count", "Count: " + 42, "Count: 42");
assert("paren", "Sum: " + (1 + 2), "Sum: 3");
assert("ltr", "Sum: " + 1 + 2, "Sum: 12");
</script>
Comparison
// Loose equality (type coercion)
5 == "5" // true — avoid this
null == undefined // true
// Strict equality (no coercion) — PREFERRED
5 === 5 // true
5 === "5" // false
null === undefined // false
// Inequality
5 != "5" // false (loose)
5 !== "5" // true (strict — preferred)
// Relational
5 > 3 // true
5 >= 5 // true
3 < 5 // true
3 <= 3 // true
// String comparison (lexicographic)
"b" > "a" // true
"banana" > "apple" // true
Use strict equality (=== and !==) to avoid subtle type coercion bugs.
Show test script
<script runat="server">
/*
* Chapter: Comparison
* Proves:
* 1. Loose vs strict equality.
* EXPECTED OUTPUT: every line starts with PASS.
*/
function assert(id, actual, expected) {
Platform.Response.Write((actual === expected ? "PASS " : "FAIL ") + id + " -> [" + actual + "]\n");
}
function assertThrows(id, fn) {
var threw = false, msg = "";
try { fn(); } catch (ex) { threw = true; msg = "" + ex.message; }
Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
assert("loose", 5 == "5" ? "true" : "false", "true");
assert("null==undef", null == undefined ? "true" : "false", "true");
assert("strict num", 5 === 5 ? "true" : "false", "true");
assert("strict mismatch", 5 === "5" ? "true" : "false", "false");
assert("null===undef", null === undefined ? "true" : "false", "false");
assert("!==", 5 !== "5" ? "true" : "false", "true");
assert(">", 5 > 3 ? "true" : "false", "true");
assert("str>", "b" > "a" ? "true" : "false", "true");
</script>
Logical Operators
// AND — returns first falsy or last truthy
true && true // true
true && false // false
"a" && "b" // "b" (last truthy)
"" && "b" // "" (first falsy)
// OR — returns first truthy or last falsy
false || true // true
"a" || "b" // "a" (first truthy)
"" || "b" // "b" (first truthy)
null || "default" // "default"
// NOT
!true // false
!false // true
!"" // true (empty string is falsy)
!"text" // false
!!value // double-negation → boolean coercion
Short-circuit defaults pattern:
// Provide fallback values using || (no ?? operator in SSJS)
var name = Platform.Request.GetQueryStringParameter("name") || "Subscriber";
var timeout = config.timeout || 30;
var debug = options.debug || false;
Note: || returns the first truthy value — 0 and "" are falsy, so this won’t work if 0 or "" are valid values. In those cases, use explicit checks:
var count = (options.count !== undefined && options.count !== null)
? options.count
: 0;
Show test script
<script runat="server">
/*
* Chapter: Logical Operators
* Proves:
* 1. && || ! returns.
* EXPECTED OUTPUT: every line starts with PASS.
*/
function assert(id, actual, expected) {
Platform.Response.Write((actual === expected ? "PASS " : "FAIL ") + id + " -> [" + actual + "]\n");
}
function assertThrows(id, fn) {
var threw = false, msg = "";
try { fn(); } catch (ex) { threw = true; msg = "" + ex.message; }
Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
assert("and", true && true ? "true" : "false", "true");
assert("and last", "a" && "b", "b");
assert("and first falsy", "" && "b", "");
assert("or first", "a" || "b", "a");
assert("or fallback", "" || "b", "b");
assert("null or", null || "default", "default");
assert("not true", !true ? "true" : "false", "false");
assert("not empty", !"" ? "true" : "false", "true");
var options = {};
var count = (options.count !== undefined && options.count !== null) ? options.count : 0;
assert("explicit 0 default", count, 0);
</script>
Ternary Operator
var message = isLoggedIn ? "Welcome back!" : "Please log in.";
// Nested (use sparingly — hard to read)
var label = count === 0 ? "none" : (count === 1 ? "one" : "many");
Show test script
<script runat="server">
/*
* Chapter: Ternary Operator
* Proves:
* 1. Ternary branches.
* EXPECTED OUTPUT: every line starts with PASS.
*/
function assert(id, actual, expected) {
Platform.Response.Write((actual === expected ? "PASS " : "FAIL ") + id + " -> [" + actual + "]\n");
}
function assertThrows(id, fn) {
var threw = false, msg = "";
try { fn(); } catch (ex) { threw = true; msg = "" + ex.message; }
Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
assert("t", true ? "Welcome back!" : "Please log in.", "Welcome back!");
assert("f", false ? "Welcome back!" : "Please log in.", "Please log in.");
assert("nested", 0 === 0 ? "none" : (1 === 1 ? "one" : "many"), "none");
</script>
Assignment Operators
var x = 5;
x += 3; // x = x + 3 → 8
x -= 2; // x = x - 2 → 6
x *= 4; // x = x * 4 → 24
x /= 6; // x = x / 6 → 4
x %= 3; // x = x % 3 → 1
Show test script
<script runat="server">
/*
* Chapter: Assignment Operators
* Proves:
* 1. += -= *= /= %=.
* EXPECTED OUTPUT: every line starts with PASS.
*/
function assert(id, actual, expected) {
Platform.Response.Write((actual === expected ? "PASS " : "FAIL ") + id + " -> [" + actual + "]\n");
}
function assertThrows(id, fn) {
var threw = false, msg = "";
try { fn(); } catch (ex) { threw = true; msg = "" + ex.message; }
Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
var x = 5; x += 3; assert("+=", x, 8); x -= 2; assert("-=", x, 6); x *= 4; assert("*=", x, 24); x /= 6; assert("/=", x, 4); x %= 3; assert("%=", x, 1);
</script>
typeof
typeof "string" // "string"
typeof 42 // "number"
typeof true // "boolean"
typeof undefined // "undefined"
typeof null // "object" (JS quirk)
typeof {} // "object"
typeof [] // "object"
typeof function(){} // "function"
Use typeof for safe existence checks:
if (typeof myVar !== "undefined") {
// myVar has been declared and assigned
}
Show test script
<script runat="server">
/*
* Chapter: typeof
* Proves:
* 1. typeof results including null quirk.
* EXPECTED OUTPUT: every line starts with PASS.
*/
function assert(id, actual, expected) {
Platform.Response.Write((actual === expected ? "PASS " : "FAIL ") + id + " -> [" + actual + "]\n");
}
function assertThrows(id, fn) {
var threw = false, msg = "";
try { fn(); } catch (ex) { threw = true; msg = "" + ex.message; }
Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
assert("str", typeof "string", "string");
assert("num", typeof 42, "number");
assert("bool", typeof true, "boolean");
assert("undef", typeof undefined, "undefined");
assert("null", typeof null, "object");
assert("obj", typeof {}, "object");
assert("arr", typeof [], "object");
assert("fn", typeof function () {}, "function");
var myVar = 1;
assert("existence check", typeof myVar !== "undefined" ? "true" : "false", "true");
</script>
Bitwise (rarely needed)
5 & 3 // 1 (AND)
5 | 3 // 7 (OR)
5 ^ 3 // 6 (XOR)
5 << 1 // 10 (left shift)
5 >> 1 // 2 (right shift)
5 >>> 0 // 5 (unsigned right shift)
Every bitwise operator in this engine fails when either operand is negative — the value’s sign, not the operator, is what breaks. &, |, ^ and ~ throw Arithmetic operation resulted in an overflow.; <<, >> and >>> throw that same message for a negative left operand and Value was either too large or too small for a UInt16. for a negative right operand. A negative value held in a variable behaves exactly like a negative literal. Guard the sign before applying any bitwise operator — see Known Bugs.
(-1) | 0 // throws: Arithmetic operation resulted in an overflow.
(-1) >>> 0 // throws: Arithmetic operation resulted in an overflow.
5 & (-1) // throws: Arithmetic operation resulted in an overflow.
5 << (-1) // throws: Value was either too large or too small for a UInt16.
Bitwise NOT never computes -(x + 1). ~0, ~1, ~2, ~5 and ~255 all return the same constant 1.84467440737096e+19 (264), so ~5 === -6 is false and even ~5 < 0 is false. Use -(x + 1) instead of ~x, and never use ~indexOf(…) as a truthiness idiom — see Known Bugs.
~5 // 1.84467440737096e+19 — expected -6
~5 === -6 // false
-(5 + 1) // -6 — the working alternative
<< also does not truncate its result to 32 bits (0x80000000 << 1 returns 4294967296, not 0), so bitwise code cannot rely on 32-bit wrap-around.
Show test script
<script runat="server">
/*
* Chapter: Bitwise
* Proves:
* 1. Positive bitwise ops match documented results.
* 2. DEV: negative operands throw.
* 3. DEV: ~ is broken; -(x+1) works.
* EXPECTED OUTPUT: every line starts with PASS.
*/
function assert(id, actual, expected) {
Platform.Response.Write((actual === expected ? "PASS " : "FAIL ") + id + " -> [" + actual + "]\n");
}
function assertThrows(id, fn) {
var threw = false, msg = "";
try { fn(); } catch (ex) { threw = true; msg = "" + ex.message; }
Platform.Response.Write((threw ? "PASS " : "FAIL ") + id + " -> " + (threw ? "threw: " + msg : "did NOT throw") + "\n");
}
assert("AND", 5 & 3, 1);
assert("OR", 5 | 3, 7);
assert("XOR", 5 ^ 3, 6);
assert("<<", 5 << 1, 10);
assert(">>", 5 >> 1, 2);
assert(">>>", 5 >>> 0, 5);
assertThrows("DEV (-1)|0 throws", function () { return (-1) | 0; });
assertThrows("DEV 5&(-1) throws", function () { return 5 & (-1); });
assertThrows("DEV 5<<(-1) throws", function () { return 5 << (-1); });
var tilde = ~5;
assert("DEV ~5 is not -6", tilde === -6 ? "true" : "false", "false");
assert("workaround -(5+1)", -(5 + 1), -6);
assert("DEV << no 32-bit wrap", (0x80000000 << 1) === 0 ? "true" : "false", "false");
</script>