← Home
🔥0 DAY
0 XP
Interview Prep · Worked Examples

SERVICENOW CODING
EXAMPLES FOR INTERVIEW.

Twenty real ServiceNow scripting problems pulled from live interview loops — each with a working script, a plain-English explanation, and an alternate approach a senior interviewer expects you to know. Skim before the call, or drill each in the live coding simulator.

  1. 1. Query active P1 incidents

    Write a script to log the number of active P1 incidents.

    Script

    var gr = new GlideRecord('incident');
    gr.addActiveQuery();
    gr.addQuery('priority', 1);
    gr.query();
    gs.info('Active P1: ' + gr.getRowCount());

    Why it works

    addActiveQuery() is shorthand for active=true. getRowCount() returns the matched count without iterating.

    Alternate approach

    Use GlideAggregate('incident') with addAggregate('COUNT') for a single DB call instead of loading rows.

  2. 2. Reassign stale incidents

    Reassign incidents in New state older than 7 days to a fallback group.

    Script

    var gr = new GlideRecord('incident');
    gr.addQuery('state', 1);
    gr.addQuery('sys_created_on', '<', gs.daysAgoStart(7));
    gr.query();
    while (gr.next()) {
      gr.assignment_group = 'fallback_group_sys_id';
      gr.update();
    }

    Why it works

    gs.daysAgoStart(7) returns a GlideDateTime 7 days back at 00:00 in system TZ, perfect for boundary queries.

    Alternate approach

    Use gr.setValue() + gr.updateMultiple() outside the loop for a single UPDATE — but you lose per-row Business Rules.

  3. 3. Count by category (GlideAggregate)

    Group incidents by category and log each count.

    Script

    var ga = new GlideAggregate('incident');
    ga.addAggregate('COUNT');
    ga.groupBy('category');
    ga.query();
    while (ga.next()) {
      gs.info(ga.category + ': ' + ga.getAggregate('COUNT'));
    }

    Why it works

    GlideAggregate pushes the GROUP BY to the DB — vastly faster than iterating with GlideRecord and counting in JS.

    Alternate approach

    For a single category, use addAggregate('COUNT') + addQuery('category', X) without groupBy.

  4. 4. Before Business Rule: derive short_description

    On insert, prefix short_description with the caller's company name.

    Script

    (function executeRule(current, previous) {
      var company = current.caller_id.company.getDisplayValue();
      if (company) current.short_description = '[' + company + '] ' + current.short_description;
    })(current, previous);

    Why it works

    In a before-BR, mutate current.* directly — the framework saves the row. Never call current.update() here (recursion).

    Alternate approach

    Use a Data Policy for cross-scope enforcement, or a Flow Designer subflow if non-devs must maintain the rule.

  5. 5. Client Script: onChange visibility

    Hide 'resolution_code' unless state is Resolved.

    Script

    function onChange(control, oldValue, newValue, isLoading) {
      if (isLoading || newValue === '') return;
      g_form.setDisplay('resolution_code', newValue === '6');
    }

    Why it works

    isLoading guards against form-load firing the change handler with the initial value. setDisplay removes the field from the DOM.

    Alternate approach

    Prefer a UI Policy — declarative, easier to maintain, and evaluates without a round trip.

  6. 6. GlideAjax: fetch manager name

    From a client script, get the manager display name of the selected user.

    Script

    var ga = new GlideAjax('UserUtils');
    ga.addParam('sysparm_name', 'getManager');
    ga.addParam('sysparm_user', g_form.getValue('caller_id'));
    ga.getXMLAnswer(function(answer) {
      g_form.setValue('u_manager_name', answer);
    });

    Why it works

    getXMLAnswer is async and returns just the 'answer' attribute — the recommended non-blocking pattern.

    Alternate approach

    For read-only reference data, use GlideRecord in the client (deprecated) or a scripted REST resource for cross-domain calls.

  7. 7. Client-callable Script Include

    Return the manager sys_id for a given user, called from GlideAjax.

    Script

    var UserUtils = Class.create();
    UserUtils.prototype = Object.extendsObject(AbstractAjaxProcessor, {
      getManager: function() {
        var u = new GlideRecord('sys_user');
        if (u.get(this.getParameter('sysparm_user'))) return u.getValue('manager');
        return '';
      },
      type: 'UserUtils'
    });

    Why it works

    Extending AbstractAjaxProcessor gives you getParameter() and the ajax framework binding. Mark 'Client callable'.

    Alternate approach

    For server-only reuse, drop AbstractAjaxProcessor and use a plain object with static helpers.

  8. 8. Encoded query with OR

    Find incidents where priority is 1 OR assignment_group is empty.

    Script

    var gr = new GlideRecord('incident');
    gr.addEncodedQuery('priority=1^ORassignment_groupISEMPTY');
    gr.query();
    gs.info(gr.getRowCount());

    Why it works

    Encoded queries mirror list filter URLs exactly — grab one from a filtered list view and paste it in.

    Alternate approach

    Chain addQuery + addOrCondition programmatically when parts of the filter come from variables.

  9. 9. Business duration between two dates

    Log business hours between opened_at and resolved_at on an incident.

    Script

    var start = new GlideDateTime(current.opened_at);
    var end = new GlideDateTime(current.resolved_at);
    var schedule = new GlideSchedule('08fcd0830a0a0b2600079f56b1adb9ae'); // 8-5 weekdays
    var dur = schedule.duration(start, end);
    gs.info('Business ms: ' + dur.getNumericValue());

    Why it works

    GlideSchedule.duration honors working hours and holidays. Numeric value is ms; divide by 3.6e6 for hours.

    Alternate approach

    Use gs.calDateDiff(startStr, endStr, false) for a quick wall-clock diff when schedules don't matter.

  10. 10. Outbound REST with error handling

    POST a payload to a vendor endpoint and log the response body.

    Script

    try {
      var r = new sn_ws.RESTMessageV2('Vendor', 'createTicket');
      r.setStringParameterNoEscape('payload', JSON.stringify({ id: current.number + '' }));
      var resp = r.execute();
      if (resp.haveError()) gs.error('Vendor error: ' + resp.getErrorMessage());
      else gs.info(resp.getBody());
    } catch (e) {
      gs.error('REST exception: ' + e.message);
    }

    Why it works

    haveError() catches HTTP-level failures; try/catch catches transport failures. Log both — silent failures are the #1 integration bug.

    Alternate approach

    For fire-and-forget, use executeAsync() + a response processor script to keep the transaction fast.

  11. 11. Cross-scope call

    From a scoped app, call a public Script Include in the global scope.

    Script

    var util = new global.GlobalUtils();
    var result = util.getConfig('billing_endpoint');
    gs.info(result);

    Why it works

    Prefix with 'global.' when accessing global-scope Script Includes; the include must be marked 'Accessible from All application scopes'.

    Alternate approach

    Expose the value via a system property and read with gs.getProperty() — no scope negotiation needed.

  12. 12. Fix script: dedupe user emails

    Deactivate duplicate sys_user records sharing the same email, keeping the oldest.

    Script

    var seen = {};
    var gr = new GlideRecord('sys_user');
    gr.addActiveQuery();
    gr.orderBy('sys_created_on');
    gr.query();
    while (gr.next()) {
      var email = (gr.email + '').toLowerCase();
      if (!email) continue;
      if (seen[email]) {
        gr.active = false;
        gr.update();
      } else {
        seen[email] = true;
      }
    }

    Why it works

    orderBy ensures the earliest record wins. Lowercasing avoids false positives from mixed-case emails.

    Alternate approach

    Use GlideAggregate to first list emails with COUNT>1, then only iterate those — faster on large tables.

  13. 13. Scheduled Job: close stale approvals

    Nightly, reject sysapproval_approver rows waiting > 14 days.

    Script

    var gr = new GlideRecord('sysapproval_approver');
    gr.addQuery('state', 'requested');
    gr.addQuery('sys_created_on', '<', gs.daysAgoStart(14));
    gr.query();
    while (gr.next()) {
      gr.state = 'rejected';
      gr.comments = 'Auto-rejected after 14 days.';
      gr.update();
    }

    Why it works

    Scheduled Scripts run as system — no ACL restrictions. Keep them idempotent so re-runs are safe.

    Alternate approach

    Fire a custom event per row via gs.eventQueue() and let a script action + notification handle it asynchronously.

  14. 14. UI Action: escalate to P1

    Add a form button that sets priority=1 and comments 'Escalated by <user>'.

    Script

    // UI Action, Client=false, Condition: current.priority > 1
    current.priority = 1;
    current.comments = 'Escalated by ' + gs.getUserDisplayName();
    current.update();
    action.setRedirectURL(current);

    Why it works

    Server UI Actions run in the same transaction as the record; action.setRedirectURL reloads the same form to show updates.

    Alternate approach

    Split into a client UI Action that shows a GlideModal confirmation, then calls a Script Include via GlideAjax.

  15. 15. Catalog Client Script: dynamic default

    On a catalog item load, default 'department' to the requester's department.

    Script

    function onLoad() {
      var ga = new GlideAjax('CatalogUtils');
      ga.addParam('sysparm_name', 'getRequesterDept');
      ga.getXMLAnswer(function(answer) {
        if (answer) g_form.setValue('department', answer);
      });
    }

    Why it works

    Catalog Client Scripts run in Service Portal and native UI. Use GlideAjax — g_user has limited fields.

    Alternate approach

    Set the default via a catalog variable's 'Default value' script — no client-side call needed.

  16. 16. Event + Script Action

    When an incident closes, queue an event that pushes a Slack notification.

    Script

    // After-BR on incident, when state changes to Closed
    gs.eventQueue('incident.closed', current, current.assigned_to + '', current.number + '');
    
    // Script Action listening on 'incident.closed'
    (function(event) {
      var r = new sn_ws.RESTMessageV2('Slack', 'post');
      r.setStringParameterNoEscape('text', 'Incident ' + event.parm2 + ' closed');
      r.execute();
    })(event);

    Why it works

    Pass sys_ids and primitives in parm1/parm2 — GlideRecord references stringify unpredictably.

    Alternate approach

    Use Flow Designer with a Slack Spoke — non-devs can maintain the flow, no BR change required.

  17. 17. ACL condition script

    Allow read on 'incident' only if the caller_id.company matches the current user's company.

    Script

    answer = false;
    if (current.caller_id.company + '' === gs.getUser().getCompanyID()) {
      answer = true;
    }

    Why it works

    ACL script assigns to 'answer'. Coerce sys_ids with + '' to strings so === works.

    Alternate approach

    Use a 'contains' query business rule on a data-driven role, or leverage Domain Separation instead of scripting ACLs.

  18. 18. Workflow scratchpad handoff

    In a workflow run script, stash a value the next activity will read.

    Script

    // Run Script activity
    workflow.scratchpad.approvers = getApprovers(current);
    
    // Next 'If' activity
    answer = workflow.scratchpad.approvers.length > 0 ? 'yes' : 'no';

    Why it works

    workflow.scratchpad persists for the lifetime of the context — cleaner than stashing on the record.

    Alternate approach

    Persist state on the parent record with a hidden field if the workflow may be replayed after a fault.

  19. 19. GlideRecordSecure for ACL-aware reads

    In a Script Include callable from portal, return incidents the current user can actually see.

    Script

    var gr = new GlideRecordSecure('incident');
    gr.addActiveQuery();
    gr.query();
    var out = [];
    while (gr.next()) out.push(gr.getValue('number'));
    return out;

    Why it works

    GlideRecordSecure applies ACLs; regular GlideRecord bypasses them when called from a system-privileged context.

    Alternate approach

    Use a scripted REST resource with 'Requires ACL' checked — the framework enforces ACLs on the endpoint itself.

  20. 20. Parse inbound JSON payload

    In a scripted REST POST, read a JSON body and create incidents.

    Script

    (function process(request, response) {
      var body = request.body.data; // parsed JSON
      var items = Array.isArray(body) ? body : [body];
      var created = [];
      items.forEach(function(item) {
        var gr = new GlideRecord('incident');
        gr.initialize();
        gr.short_description = item.title;
        gr.caller_id = item.caller;
        created.push(gr.insert());
      });
      return { created: created };
    })(request, response);

    Why it works

    request.body.data is already parsed. Normalize to an array so single-item and batch requests share one code path.

    Alternate approach

    For high-volume ingest, insert rows via the Import Set API + a transform map — offloads validation to platform.

Practice these live

Drill 2,000+ variants with instant validation and alternative-approach hints.