A custom function that finishes in 2 seconds against 500 records and dies after 8 minutes against 50,000 is almost always the same three bugs. Loop shape matters more than logic.
Bug 1: searchRecords Without Pagination Bounds
zoho.crm.searchRecords returns up to 200 per call. If you call it once and assume you got everything, you get exactly 200. Always paginate explicitly:
page = 1;
all_records = list();
while(true)
{
batch = zoho.crm.searchRecords("Leads", "(Status:equals:Open)", page, 200);
if(batch.size() == 0) break;
all_records.addAll(batch);
page = page + 1;
}
Don’t use getRecords for large pulls — it does not support filter expressions and you’ll loop the entire module.
Bug 2: Updating Inside the Loop
A single zoho.crm.updateRecord per iteration is the most common kill switch. Each call is a network round-trip and counts against your API credit pool. Batch with bulkUpdate when the payload allows:
update_payload = list();
for each rec in all_records
{
update_payload.add({"id": rec.get("id"), "Score": rec.get("Score") + 10});
}
zoho.crm.bulkUpdate("Leads", update_payload);
Bulk operations cap at 100 records per call, so chunk accordingly.
Bug 3: Map Mutations Inside Iterations
Modifying a map you’re iterating creates non-deterministic behavior in Deluge — particularly with nested for each loops. Build the result map outside the loop, assign once at the end.
Watch Your Execution Limits
Custom functions have a hard 5-minute wall and a 3,000 statement-per-invocation soft cap. If you’re approaching either, switch to a scheduled function that processes a chunk and re-schedules itself.
What to Do This Week
- Grep your org for any
searchRecordscall without an explicitwhileloop. - Replace single-record updates inside loops with
bulkUpdatechunks of 100. - Add a statement counter (
infolog every 500 iterations) to your slowest function. - Convert anything over 30s of expected runtime into a self-rescheduling job.