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

  1. Cache loop lengthvar len = arr.length;
  2. Cache repeated property accessesvar name = person.profile.name; before the loop
  3. Avoid creating objects inside tight loops when possible
  4. WSProxy batching — use createBatch/updateBatch instead of looping over individual creates/updates
  5. Limit DE row retrieval — use LookupOrderedRows with a sortCount limit 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>