The N+1 query problem is ServiceNow's most common performance killer β you execute one query to get a list of records, then execute N additional queries inside the loop to fetch related data. What looks like innocent lookups becomes hundreds of database hits that bring your instance to its knees. The platform doesn't warn you about this; it just starts timing out your scripts when the query volume gets ridiculous. The solution is always the same: collect all the IDs you need first, then use addQuery('field', 'IN', ids) to batch the lookup into a single query.
When to use this
- When you need to enrich records with data from related tables in a Business Rule or Script Include
- When processing a bounded set of records where you control the input size (under 1000 records)
- When you need multiple field values from the related records, not just existence checks
- When running in Scheduled Jobs that process batches of records for data cleanup or reporting
When NOT to use this
- Don't use this in Client Scripts β use
GlideAjaxto call a Script Include that does the batching server-side - Don't use this when you only need counts or existence checks β use
GlideAggregateorGlideRecord.hasNext() - Don't use this for unbounded result sets β you'll hit memory limits before you hit query limits
- Don't batch reference field lookups that are already cached by
getDisplayValue()β you're optimizing something that's already fast
Key behaviors and gotchas
- The
INoperator expects a comma-separated string, not an array β always usejoin(',') - Reference fields return
GlideElementReferenceobjects β calltoString()to get the sys_id for comparisons - Empty reference fields and deleted records will create gaps in your lookup map β always provide fallback values
- ACLs still apply to batch queries β records the user can't read will silently disappear from results
- Domain separation affects
INqueries β records in different domains may not be visible even with valid sys_ids - Large
INclauses (over 1000 values) can hit database query length limits β chunk them if needed
Building lookup maps with sys_id keys requires toString() on both sides β GlideRecord sys_id fields and reference field values are objects, not strings. Missing toString() calls create lookup failures that are invisible until you test with real data.
Enjoying this? Get one deep-dive per week.
Join 1,000+ ServiceNow pros β scripts, GlideRecord patterns, Flow Designer techniques, and career moves. Free.