Skip to content
ServiceNow Interview Q&A 15 min read 11 sections

ServiceNow Script Include and GlideAjax Interview Questions

This is the scripting round for a ServiceNow developer or senior admin role. The panel has finished with business rules and client scripts, and now wants to see whether you understand where your code runs and what it costs the instance. The twenty questions below come from real interview rounds coll

RL

RizeX Labs

RizeX Labs

Published

Last updated

Related programme

ServiceNow Training

ITSM, CMDB, scripting and Flow Designer taught on a live instance, with a build project and interview practice before profiles reach the hiring partners.

See the curriculum

3000+ learners · 270+ hiring partners

This is the scripting round for a ServiceNow developer or senior admin role. The panel has finished with business rules and client scripts, and now wants to see whether you understand where your code runs and what it costs the instance. The twenty questions below come from real interview rounds collected by our trainers and cover Script Includes, client callable classes, GlideAjax, scoped application access, GlideAggregate and query tuning. Each answer gives the definition, the reason it matters, then code or a table where the question deserves one.

Script Include questions

What is Script Include?

A Script Include is a reusable server-side script stored on the sys_script_include table. You write the logic once and call it from business rules, UI actions, scheduled jobs, flow actions, transform maps, scripted REST APIs and client scripts through GlideAjax.

It loads only when something calls it, unlike a business rule, which is tied to a table and fires on a database operation. Fields that matter on the record: Name (must match the class name exactly), API Name, Client callable, Accessible from, Active and Protection policy.

Types of Script Includes?

Type How it is written Typical use
Classless / on-demand function A function assigned to a variable of the same name Small helper used in one or two places
Class based (prototype) Class.create() plus a prototype object Reusable server-side utility
Client callable Extends AbstractAjaxProcessor Called from a client script through GlideAjax
Extending another Script Include Object.extendsObject(ParentClass, {...}) Adding behaviour to an OOB or custom class

A plain class based Script Include:

var IncidentUtils = Class.create();
IncidentUtils.prototype = {
    initialize: function() {},

    isMajor: function(incidentGr) {
        return incidentGr.getValue('priority') === '1' &&
               incidentGr.getValue('impact') === '1';
    },

    type: 'IncidentUtils'
};

Difference between client callable and server side Script Include?

A client callable Script Include has the Client callable checkbox ticked and extends AbstractAjaxProcessor. Only that kind can be reached from browser-side code. A normal server side Script Include has the box unticked and is callable only from other server-side script.

Point Client callable Server side only
Client callable field Checked Unchecked
Extends AbstractAjaxProcessor Nothing, or another class
Reads input using this.getParameter('sysparm_x') Normal function arguments
Returns A string or XML to the browser Any JavaScript value or object
Called from Client script, catalog client script, server script Server script only
Exposure Reachable by any logged-in user Not reachable from the browser

The last row is the one candidates forget. Ticking Client callable turns the class into an endpoint. Do not tick it out of habit.

How to call Script Include from client script?

Here is the complete matched pair. First the client callable Script Include:

var CallerAjaxUtil = Class.create();
CallerAjaxUtil.prototype = Object.extendsObject(global.AbstractAjaxProcessor, {

    getCallerDetails: function() {
        var callerId = this.getParameter('sysparm_caller_id');
        var result = {};

        var usr = new GlideRecord('sys_user');
        if (usr.get(callerId)) {
            result.email    = usr.getValue('email');
            result.vip      = usr.getValue('vip');
            result.location = usr.getDisplayValue('location');
        }

        return JSON.stringify(result);
    },

    type: 'CallerAjaxUtil'
});

Now the onChange client script that calls it asynchronously with a callback:

function onChange(control, oldValue, newValue, isLoading, isTemplate) {
    if (isLoading || newValue === '') {
        return;
    }

    var ga = new GlideAjax('CallerAjaxUtil');
    ga.addParam('sysparm_name', 'getCallerDetails');
    ga.addParam('sysparm_caller_id', newValue);

    ga.getXMLAnswer(function(answer) {
        if (!answer) {
            return;
        }

        var caller = JSON.parse(answer);
        g_form.setValue('u_caller_email', caller.email);
        g_form.setValue('location', caller.location);

        if (caller.vip === 'true') {
            g_form.addInfoMessage('This caller is marked VIP.');
        }
    });
}

Four things the panel listens for. sysparm_name carries the method name and nothing else. Your own parameter names start with sysparm_. getXMLAnswer hands you the answer string, getXML the whole response object. And every line that uses the result lives inside the callback, never after it. If the Script Include is in global and the client script is in a scoped application, pass the API name: new GlideAjax('global.CallerAjaxUtil').

How to secure Script Includes?

  • Leave Client callable unchecked unless a client script genuinely needs the class. An exposed method that queries any table is a data leak.
  • Set Accessible from to This application scope only by default.
  • Use the Protection policy field (Read only or Protected) on code you ship in an application.
  • Check the caller with gs.hasRole('itil') before returning sensitive data, and validate whatever arrives through this.getParameter().
  • Query with GlideRecordSecure when the data is ACL controlled, and return only the fields the client needs.

What the interviewer is actually checking

  • Whether you can name the sys_script_include fields, not just describe the concept.
  • Whether you treat the Client callable checkbox as a security decision.
  • Whether you write type: at the end of the prototype, which shows you have written one rather than read about one.
  • Whether your security answer goes past "we set ACLs".

GlideAjax questions

Difference between synchronous and asynchronous GlideAjax?

Asynchronous GlideAjax uses getXML(callback) or getXMLAnswer(callback). The call goes to the server, the browser carries on, and the callback runs when the response arrives. Synchronous GlideAjax uses getXMLWait() followed by getAnswer(), and the browser stops and waits.

// Synchronous. Avoid this.
var ga = new GlideAjax('CallerAjaxUtil');
ga.addParam('sysparm_name', 'getCallerDetails');
ga.addParam('sysparm_caller_id', newValue);
ga.getXMLWait();
var answer = ga.getAnswer();

Give all three reasons it is discouraged. It freezes the browser tab until the server replies, so a slow query becomes a frozen form. It is not available in scoped applications, so the code breaks the day your work moves into a scoped app. And it does not work in Service Portal or the mobile and Next Experience interfaces, which run a different client runtime.

Our trainers see the same gap every batch: candidates know the syntax but not the execution. They write both versions and still cannot say when the code runs, where it runs and what data it can reach.

How to send more than one variable from server side script to client side while using GlideAjax?

A GlideAjax method returns one value, so pack several into one string. Two accepted ways.

Return a JSON string. Build an object on the server, JSON.stringify it, JSON.parse it in the callback. That is the pattern in the matched pair above, and most teams use it because it survives nested data.

Build XML with newItem(). AbstractAjaxProcessor gives you a response document to add elements and attributes to:

getCallerXML: function() {
    var usr = new GlideRecord('sys_user');
    if (!usr.get(this.getParameter('sysparm_caller_id'))) {
        return;
    }

    var item = this.newItem('caller');
    item.setAttribute('email', usr.getValue('email'));
    item.setAttribute('vip', usr.getValue('vip'));
}

Read it with getXML, because you need the whole response document:

ga.getXML(function(response) {
    var caller = response.responseXML.getElementsByTagName('caller')[0];
    if (caller) {
        g_form.setValue('u_caller_email', caller.getAttribute('email'));
    }
});

Note the pairing. getXMLAnswer goes with a returned string, getXML with newItem elements.

Why won't we be able to stop form submission while validating form data via GlideAjax call?

Because an onSubmit client script is synchronous and GlideAjax is not. The function must return true or false immediately. An asynchronous call returns nothing at that moment, so the function finishes, returns undefined, the form submits, and the callback arrives after the record is saved.

The fix is to cancel the first submit and submit again from inside the callback:

var callbackDone = false;

function onSubmit() {
    if (callbackDone) {
        return true;              // second pass, validation already cleared
    }

    var ga = new GlideAjax('CallerAjaxUtil');
    ga.addParam('sysparm_name', 'isCallerActive');
    ga.addParam('sysparm_caller_id', g_form.getValue('caller_id'));

    ga.getXMLAnswer(function(answer) {
        if (answer === 'true') {
            callbackDone = true;
            g_form.submit();
        } else {
            g_form.addErrorMessage('Selected caller is not active.');
        }
    });

    return false;                 // stop this submit
}

Better still, move the check to a before business rule with current.setAbortAction(true). Server-side validation cannot be bypassed by a user who disables client script, and it covers records created by import or API.

Did you face challenges while using GlideAjax?

An experience question. Answer it as situation, approach, reason, result, not as a definition.

A usable answer: on a catalog item, an onChange script fired GlideAjax on every keystroke in a reference field, so the form felt slow. The approach was to move part of the lookup into the reference qualifier and cache the returned object so the same sys_id was not fetched twice. The reason was that the query itself was cheap and the call volume was the real problem. The result was one server call per caller selection instead of one per keystroke.

Other challenges worth naming if they are genuinely yours: the answer arriving as a string, so if (answer) was true even for 'false'; a Script Include that worked in global and failed from a scoped client script until the API name prefix was added.

Debugging separates the answers here. "Your GlideAjax returns nothing, what do you check?" beats "what is GlideAjax" every time. Check the Client callable box, the method name in sysparm_name, whether the method returns or only assigns, and the xmlhttp.do call in the browser network tab.

What the interviewer is actually checking

  • Whether you give all three reasons synchronous GlideAjax is discouraged, not just "it is slow".
  • Whether you know the callback is the only safe place to use the answer, and that the answer is always a string.
  • Whether you push validation to the server instead of fighting the onSubmit timing.
  • Whether your experience answer names a table, a requirement and a reason.

Scoped application and GlideRecord questions

Difference between gr.next(), gr.hasNext() and gr._next() methods?

next() moves the cursor to the next record and returns true if there was one. It is what drives while (gr.next()). hasNext() reports whether another record exists without moving the cursor.

_next() does the same job as next(). It exists for tables that have a field actually named next, where gr.next() would be read as the field instead of the method. The same reasoning gives you _query() for tables with a field named query.

How to allow accessing data from one scoped application to another scoped application?

Cross-scope access is deliberate and granted on both sides.

  1. On the table record in the source application, open the Application Access section, set Accessible from to All application scopes and tick Can read, Can create, Can update or Can delete as needed.
  2. On a Script Include the other scope must call, set Accessible from to All application scopes.
  3. The calling application needs a cross-scope privilege record. When that scope first attempts the operation at runtime, the platform creates an entry in the cross-scope privileges list, which an admin then sets to Allowed.
  4. Reference the target by API name, for example new global.IncidentUtils().

Skip step 3 and the call fails with a security exception even though the table looks open.

What is Scoped App and Global App?

A scoped application lives in its own namespace with a prefix such as x_abc_hrtool. Its tables and script includes carry that prefix, and by default nothing outside the scope can touch them. A global application has no prefix and no boundary, so everything in global can be reached by anything else, which is why older instances end up with tangled customisations.

Point Scoped Global
Namespace prefix Yes, x_... No
Access to other scopes Granted explicitly Open
Restricted APIs Some legacy APIs blocked, such as getXMLWait() Available
Best for New applications, anything you distribute Platform-wide changes to OOB tables

What the interviewer is actually checking

  • Whether you know why _next() exists, which only someone who hit the problem can explain.
  • Whether you list both sides of cross-scope access, table plus privilege record.
  • Whether you know which APIs scoping takes away from you.

Advanced scripting questions

What is GlideAggregate?

GlideAggregate is the server-side class for aggregate queries. It asks the database for COUNT, SUM, AVG, MIN or MAX instead of returning rows, so the work happens in the database and not in your loop.

var ga = new GlideAggregate('incident');
ga.addQuery('active', true);
ga.addAggregate('COUNT', 'priority');
ga.groupBy('priority');
ga.query();

while (ga.next()) {
    gs.info(ga.getValue('priority') + ' : ' + ga.getAggregate('COUNT', 'priority'));
}

Difference between GlideRecord and GlideAggregate?

Point GlideRecord GlideAggregate
Returns Records with field values Aggregate numbers
Can update data Yes No, read only
Counting rows getRowCount() after loading rows addAggregate('COUNT'), no rows loaded
Grouping Not supported natively groupBy()
Cost on a large table High, rows return to the node Low, the database does the work

The rule of thumb: the moment you only want a number, stop using GlideRecord.

What is Encoded Query?

An encoded query is a filter written as a single string, for example active=true^priority=1^ORDERBYnumber, applied with addEncodedQuery().

var gr = new GlideRecord('incident');
gr.addEncodedQuery('active=true^priority=1^assignment_group.nameSTARTSWITHNetwork');
gr.query();

Build one by creating the filter on the list view, then right click the breadcrumb and choose Copy query. getEncodedQuery() returns the same format, useful for logging what a script actually ran.

How to optimize GlideRecord queries?

  • Filter in the query, not in JavaScript. Every missing addQuery() means rows travelling to the node so an if can throw them away.
  • Use setLimit() when you need the first N rows, and gr.get(sys_id) for exactly one record.
  • Use GlideAggregate for counts instead of query() followed by getRowCount().
  • Never run a query inside a loop over another query. Build a lookup object in one pass, or use an encoded query with an IN clause.
  • Query on indexed fields. A CONTAINS on a long text field is not indexed and forces a full scan.
  • For bulk updates use setWorkflow(false) so business rules and notifications do not fire on every record.

What is gs.getProperty()?

gs.getProperty('property.name') reads a value from the System Properties table (sys_properties) in server-side script. The second argument is the default used when the property does not exist.

var endpoint = gs.getProperty('x_abc_app.integration.endpoint', 'https://example.com/api');
var retries  = parseInt(gs.getProperty('x_abc_app.integration.retries', '3'), 10);

It always returns a string, so parse before comparing numbers or booleans. And it keeps environment-specific values out of code, so the same update set behaves correctly in dev, test and production.

What the interviewer is actually checking

  • Whether you reach for GlideAggregate unprompted when the answer is a number.
  • Whether your optimisation list is about the database, not about tidier code.
  • Whether you treat properties as configuration rather than constants in script.

Performance and optimization questions

What causes performance issues in ServiceNow?

Most of it is work the instance did not need to do.

  • Unbounded or unindexed queries, especially CONTAINS on large tables and queries inside a loop.
  • Synchronous GlideAjax, and onLoad client scripts that call the server before the form paints.
  • Too many business rules on a hot table, and before-query rules that add conditions to every read.
  • Large tables that were never archived, with related lists and reports running over all of it.
  • Heavy dot-walking in list views, because each dot-walked column is a join.
  • Long scheduled jobs and integrations all set to the same clock time.

How do you optimize forms and lists?

  • Reduce fields and sections, moving rarely used fields into a collapsed second section.
  • Replace onLoad client scripts with UI Policies wherever the logic is show, hide, mandatory or read only.
  • Remove server calls from onLoad. If a value is needed at load time, calculate it in a display business rule and pass it through g_scratchpad.
  • Trim related lists, because each one is a separate query on form load.
  • Trim list columns, particularly dot-walked ones, and lower the rows per page default.
  • Index the fields users filter and sort on most, after confirming the cost in the slow query log.

How to handle large data volumes?

  • Define a retention policy and use table rotation or the Table Cleaner for high-volume logging tables.
  • Archive rather than delete where audit rules require history, so the working table stays small.
  • Process in batches. Use setLimit() with a watermark such as the last processed sys_created_on and let a scheduled job resume from there.
  • Use GlideAggregate for reporting numbers, never a row loop.
  • For bulk loads use import sets, keep transform scripts light, and index the columns your batching query filters on.

What the interviewer is actually checking

  • Whether you name specific causes instead of saying "too many customisations".
  • Whether you separate fixing the form from fixing the query behind it.
  • Whether you have a batching pattern ready, which is where most candidates stop.
  • Whether you measure, through the slow query log or session debug, before changing anything.

Preparing for this round

Write the client callable Script Include and its matching client script by hand until you can produce both without looking, because the panel will ask you to write them on a shared screen. Then attach one real example from your own work to every answer above, naming the table, the requirement and the reason you chose that approach. Memorised definitions hold for about thirty seconds and collapse the moment the interviewer says "give me a real project example".

For structured practice on Script Includes, GlideAjax, scoping and query tuning with review on the code you write, our ServiceNow training programme covers this scripting round along with the ITSM and integration rounds that follow it.