Loops
for, while, do...while, for...in, performance tips, and loop patterns for SFMC SSJS.
for Loop
The classic for loop is the most common in SSJS:
var rows = Platform.Function.LookupRows("MyDE", "Status", "active");
for (var i = 0; i < rows.length; i++) {
var row = rows[i];
Write(row["Email"] + "<br>");
}
Cache the Length
Always cache arr.length in a variable before the loop. The SSJS engine re-evaluates the condition expression on every iteration. For arrays from Platform functions this is a real performance difference:
// ⚠️ Slow — length re-evaluated on each iteration
for (var i = 0; i < rows.length; i++) { /* ... */ }
// ✅ Fast — length cached once
var len = rows.length;
for (var i = 0; i < len; i++) { /* ... */ }
Reverse Loop (when order doesn’t matter)
Slightly more efficient because it only compares against 0:
var len = items.length;
for (var i = len - 1; i >= 0; i--) {
process(items[i]);
}
Show test script
<script runat="server">
/*
* Chapter: for Loop
* Proves:
* 1. for + cached length.
* 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 rows = [{ Email: "a@x.com" }, { Email: "b@x.com" }], out = "", len = rows.length;
for (var i = 0; i < len; i++) out += rows[i].Email + ",";
assert("for", out, "a@x.com,b@x.com,");
var items = [1, 2, 3], rev = "";
for (var j = items.length - 1; j >= 0; j--) rev += items[j];
assert("reverse", rev, "321");
</script>
while Loop
Use when the number of iterations is not known upfront:
var page = 1;
var hasMore = true;
while (hasMore) {
var result = fetchPage(page);
processResults(result.items);
if (result.items.length < 200) {
hasMore = false;
} else {
page++;
}
}
Show test script
<script runat="server">
/*
* Chapter: while
* Proves:
* 1. while exits.
* 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 n = 0; while (n < 3) n++; assert("while", n, 3);
</script>
do…while Loop
Executes the body at least once before checking the condition:
var attempts = 0;
var success = false;
do {
success = tryOperation();
attempts++;
} while (!success && attempts < 3);
Show test script
<script runat="server">
/*
* Chapter: do...while
* Proves:
* 1. runs at least once.
* 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 attempts = 0, success = false;
do { attempts++; if (attempts >= 2) success = true; } while (!success && attempts < 3);
assert("attempts", attempts, 2);
</script>
for…in Loop
Iterates over the enumerable properties of an object. Always use hasOwnProperty to skip inherited properties:
var config = { timeout: 30, retries: 3, debug: false };
for (var key in config) {
if (config.hasOwnProperty(key)) {
Write(key + " = " + config[key] + "<br>");
}
}
Do not use for...in to iterate arrays — it iterates property names (indices as strings), not values, and inherited Array prototype properties may appear:
// ❌ Bad: for...in on arrays
var arr = [10, 20, 30];
for (var k in arr) {
Write(arr[k]); // Works accidentally, but fragile
}
// ✅ Good: for loop on arrays
for (var i = 0; i < arr.length; i++) {
Write(arr[i]);
}
Show test script
<script runat="server">
/*
* Chapter: for...in
* Proves:
* 1. enumerates keys; array for-in is fragile vs indexed for.
* 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 config = { a: 1, b: 2 }, keys = "";
for (var key in config) keys += key;
assert("has a", keys.indexOf("a") >= 0 ? "true" : "false", "true");
var arr = ["x", "y"], indexed = "";
for (var i = 0; i < arr.length; i++) indexed += arr[i];
assert("indexed for", indexed, "xy");
</script>
for…of — NOT SUPPORTED
for...of is ES6 and not available in SSJS. The linter will flag it.
// ❌ Not supported — do not uncomment:
// for (var item of items) { }
// ✅ Use for loop instead
for (var i = 0; i < items.length; i++) {
var item = items[i];
}
Show test script
<script runat="server">
/*
* Chapter: for...of NOT SUPPORTED
* Proves:
* 1. Documented as unsupported — chapter is negative guidance only.
* NON-ASSERTABLE: cannot execute for-of without parse abort.
* 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("guidance recorded", "use-indexed-for", "use-indexed-for");
</script>
break and continue
// break — exit the loop
for (var i = 0; i < 100; i++) {
if (found) { break; }
}
// continue — skip to next iteration
for (var i = 0; i < items.length; i++) {
if (!items[i].active) { continue; } // skip inactive
processItem(items[i]);
}
// Labeled break (exit outer loop from inner)
outer: for (var i = 0; i < rows.length; i++) {
for (var j = 0; j < cols.length; j++) {
if (found) { break outer; }
}
}
Show test script
<script runat="server">
/*
* Chapter: break/continue
* Proves:
* 1. break/continue.
* 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 sum = 0; for (var i = 0; i < 10; i++) { if (i === 5) break; sum += i; }
assert("break", sum, 10);
var kept = 0; for (var j = 0; j < 5; j++) { if (j % 2 === 0) continue; kept++; }
assert("continue", kept, 2);
</script>
Automation Timeout Guard
Script Activities in Automation Studio have a 30-minute hard timeout. For long-running batch processes, monitor elapsed time and break:
<script runat="server">
Platform.Load("core", "1.1.5");
var startTime = new Date();
var MAX_MINUTES = 25;
var proxy = new Script.Util.WSProxy();
var result = proxy.retrieve("DataExtension", ["CustomerKey", "Name"]);
var allDEs = result.Results;
var processed = 0;
var len = allDEs.length;
for (var i = 0; i < len; i++) {
var elapsed = (new Date() - startTime) / 60000;
if (elapsed >= MAX_MINUTES) {
Write("Time limit reached. Processed " + processed + " of " + len);
break;
}
// Process each DE
processDE(allDEs[i]);
processed++;
}
Write("Done: " + processed + " processed.");
</script>
Show test script
<script runat="server">
/*
* Chapter: Timeout Guard
* Proves:
* 1. early exit at cap.
* 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 processed = 0, max = 10;
for (var i = 0; i < 100; i++) { processed++; if (processed >= max) break; }
assert("cap", processed, 10);
</script>
Performance Tips
- Cache loop length —
var len = arr.length; - Cache repeated property accesses —
var name = person.profile.name;before the loop - Avoid creating objects inside tight loops when possible
- WSProxy batching — use
createBatch/updateBatchinstead of looping over individual creates/updates - Limit DE row retrieval — use
LookupOrderedRowswith asortCountlimit instead of retrieving all rows
// Retrieve only the first 100 rows, newest first
var rows = Platform.Function.LookupOrderedRows(
"MyDE",
100, // max rows
"CreatedDate", // sort field
"DESC", // sort direction
"Active", // filter field
"1" // filter value
);
Show test script
<script runat="server">
/*
* Chapter: Performance Tips
* Proves:
* 1. cached length used.
* 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 rows = [1, 2, 3, 4], len = rows.length, n = 0;
for (var i = 0; i < len; i++) n += rows[i];
assert("sum", n, 10);
</script>