SFMC enforces execution time limits on SSJS scripts. CloudPages typically have a 30-second timeout, and Automation Studio scripts have a 30-minute limit (but individual script activities may have shorter limits). Understanding and working within these limits is critical.

1. Minimize DE Round-Trips

Every Platform.Function.Lookup, de.Rows.Retrieve, and WSProxy call involves a network round-trip to SFMC servers. Minimize them.

Batch reads instead of per-row lookups

// SLOW — one lookup per row
for (var i = 0; i < ids.length; i++) {
    var record = Platform.Function.Lookup("DE", "data", "id", ids[i]);
    // process record
}

// FASTER — retrieve all needed rows at once
var rows = Platform.Function.LookupRows("DE", "Status", "active");
var lookupMap = {};
for (var i = 0; i < rows.length; i++) {
    lookupMap[rows[i].id] = rows[i];
}

// Then use lookupMap[id] instead of a per-row Lookup

Use LookupOrderedRows with a limit

// Get only what you need
var recent = Platform.Function.LookupOrderedRows(
    "Events", 10, "Timestamp desc",
    "UserId", userId
);

2. Cache Expensive Results

If you need to use the same DE data multiple times, load it once at the top of the script:

Platform.Load("core", "1.1.5");

// Load config once
var config = {};
var configRows = Platform.Function.LookupRows("AppConfig", "Active", "true");
for (var i = 0; i < configRows.length; i++) {
    config[configRows[i].Key] = configRows[i].Value;
}

// Use config throughout without further lookups
var timeout = config["requestTimeout"] || "30000";
var apiKey = config["apiKey"];

3. Efficient Loops

Cache array length before looping:

// CORRECT — length evaluated once
var len = items.length;
for (var i = 0; i < len; i++) {
    process(items[i]);
}

// LESS EFFICIENT — length re-evaluated each iteration
for (var i = 0; i < items.length; i++) {
    process(items[i]);
}

Break early when possible:

var found = null;
for (var i = 0; i < rows.length; i++) {
    if (rows[i].Id === targetId) {
        found = rows[i];
        break;  // stop scanning
    }
}

4. HTTP Calls Are Expensive

Each HTTP request (to external APIs, SFMC REST API, etc.) adds significant latency. Minimize calls:

// BAD — multiple calls for same data
var name = callApi("/user/" + id + "/name");
var email = callApi("/user/" + id + "/email");
var prefs = callApi("/user/" + id + "/prefs");

// BETTER — single call
var user = callApi("/user/" + id);
var name = user.name;
var email = user.email;
var prefs = user.prefs;

Cache auth tokens in a DE rather than fetching a new token on every page load:

function getAccessToken() {
    // Check for valid cached token
    var cached = Platform.Function.Lookup("TokenCache", "token",
        "service", "sfmcRest");
    var expiry = Platform.Function.Lookup("TokenCache", "expires",
        "service", "sfmcRest");

    if (cached && expiry && new Date(expiry) > new Date()) {
        return cached;
    }

    // Fetch new token
    var resp = Platform.Function.HTTPPost(authUrl, "application/json",
        Stringify({ grant_type: "client_credentials",
                    client_id: clientId, client_secret: clientSecret }));
    var token = Platform.Function.ParseJSON(resp + "");

    // Cache it
    Platform.Function.UpsertData("TokenCache",
        ["service"], ["sfmcRest"],
        ["token", "expires"],
        [token.access_token, Platform.Function.FormatDate(
            Platform.Function.DateAdd(Platform.Function.Now(),
                token.expires_in - 60, "S"),
            "MM/DD/YYYY HH:mm:ss")]
    );

    return token.access_token;
}

5. Automation Studio Timeout Guard

Long-running automations can time out mid-loop. Add a time guard:

var startTime = new Date().getTime();
var MAX_RUNTIME_MS = 25 * 60 * 1000; // 25 minutes

for (var i = 0; i < rows.length; i++) {
    var elapsed = new Date().getTime() - startTime;
    if (elapsed > MAX_RUNTIME_MS) {
        // Save progress marker and exit gracefully
        Platform.Function.UpsertData("ProcessingState",
            ["jobId"], [jobId],
            ["lastProcessed", "status"],
            [rows[i].id, "paused"]
        );
        break;
    }
    processRow(rows[i]);
}

6. String Concatenation in Loops

Building large strings with += in a loop is slow for very large outputs. For large HTML generation:

// For moderate output (< a few thousand characters), += is fine
var html = "";
for (var i = 0; i < items.length; i++) {
    html += "<li>" + items[i].name + "</li>";
}
Write(html);

For very large outputs (thousands of items), use Write() directly in the loop to avoid a large string in memory:

Write("<ul>");
for (var i = 0; i < items.length; i++) {
    Write("<li>" + items[i].name + "</li>");
}
Write("</ul>");

7. Avoid Unnecessary Platform.Load

Platform.Load("core", "1.1.5") has a small overhead. Call it once per page, not multiple times:

// CORRECT — called once
Platform.Load("core", "1.1.5");

// WRONG — redundant calls add overhead
Platform.Load("core", "1.1.5");
var de = DataExtension.Init("DE1");
Platform.Load("core", "1.1.5"); // don't call again
var sub = Subscriber.Init("sub_123");

8. Prefer Platform.Function for Single Lookups

For single-row lookups, Platform.Function.Lookup is faster than DataExtension.Init + Rows.Retrieve:

// Faster for single value
var email = Platform.Function.Lookup("Contacts", "Email", "Id", contactId);

// Slower for single value (Core initializes more objects)
var de = DataExtension.Init("Contacts");
var rows = de.Rows.Retrieve({ Property: "Id", SimpleOperator: "equals", Value: contactId });
var email = rows[0] ? rows[0].Email : "";

See Also