ServiceNow Scripting Use Cases for Interview

0

ServiceNow ITSM Scenario-Based Interview Questions 2025

Real-world scenarios every ServiceNow developer gets asked in interviews — with clear answers, working code, and pro tips. No fluff. Just what actually gets asked.

Q1. A major incident is raised but no one is acknowledging it. The manager wants an auto-assign mechanism. How do you handle this in ServiceNow?

Incident Management  |  Intermediate

Use Assignment Rules as the first approach. Go to System Policy → Rules → Assignment, target the Incident table, set your conditions (e.g. Category = Network), and assign the correct group. As a fallback, use a Business Rule:

// Business Rule: Auto-assign if assignment group is empty
// Table: Incident | When: Before Insert/Update

(function executeRule(current, previous) {
  if (!current.assignment_group || current.assignment_group.nil()) {
    var gr = new GlideRecord('sys_user_group');
    gr.addQuery('name', 'IT Support');
    gr.query();
    if (gr.next()) {
      current.assignment_group = gr.sys_id;
    }
  }
})(current, previous);

💡 Pro Tip

Always use Assignment Rules first. Business Rules are a fallback. This keeps logic clean and maintainable.

Q2. A VIP user raises an incident. The business wants it automatically set to Priority 1 and the manager gets an email. How do you build this?

Incident Management  |  Advanced

Tag users as VIP using a custom field u_is_vip on the User table. Then write a Business Rule on the Incident table:

// Business Rule: VIP Auto Priority
// Table: Incident | When: Before Insert

(function executeRule(current, previous) {
  var caller = new GlideRecord('sys_user');
  if (caller.get(current.caller_id)) {
    if (caller.u_is_vip == true) {
      current.priority   = '1';
      current.urgency    = '1';
      current.impact     = '1';
      current.work_notes = 'Auto-escalated: VIP caller detected.';
    }
  }
})(current, previous);

💡 Pro Tip

Create a Notification: Table = Incident, When = Record inserted, Condition = Priority is 1. Send to the caller's manager using the 'Manager of Caller' field in the email template.

Q3. The business says: "No change should go to production on a Friday after 3 PM." How do you enforce this in ServiceNow?

Change Management  |  Intermediate

Use a Business Rule on the Change Request table that checks the day and time before allowing the state to move to Implement:

// Business Rule: Block Friday Change
// Table: Change Request | When: Before Update

(function executeRule(current, previous) {
  if (current.state == '3' && previous.state != '3') {
    var now  = new GlideDateTime();
    var day  = now.getDayOfWeekLocalTime(); // 1=Sun, 6=Fri, 7=Sat
    var hour = parseInt(now.getLocalTime().getByFormat('HH'));

    if (day == 6 && hour >= 15) {
      current.setAbortAction(true);
      gs.addErrorMessage(
        'Change cannot move to Implement on Friday after 3 PM. Please reschedule.'
      );
    }
  }
})(current, previous);

💡 Pro Tip

You can also handle this via Change Approval Policies or Flow Designer. The Business Rule approach is fastest to implement and easiest to explain in interviews.

Q4. Three incidents with the same error code come in within one hour. How do you auto-create a Problem record and link all incidents?

Problem Management  |  Intermediate

Use a Business Rule (After Insert on Incident) that counts matching incidents in the last hour and auto-creates a Problem when the threshold is hit:

// Business Rule: Auto-create Problem
// Table: Incident | When: After Insert

function autoCreateProblem() {
  var errorCode = current.u_error_code;
  if (!errorCode || errorCode == '') return;

  var incCount = new GlideAggregate('incident');
  incCount.addQuery('u_error_code', errorCode);
  incCount.addQuery('state', 'IN', '1,2');
  var oneHourAgo = new GlideDateTime();
  oneHourAgo.subtract(3600000);
  incCount.addQuery('sys_created_on', '>=', oneHourAgo);
  incCount.addAggregate('COUNT');
  incCount.query();

  if (incCount.next() && incCount.getAggregate('COUNT') >= 3) {
    var prob = new GlideRecord('problem');
    prob.addQuery('u_error_code', errorCode);
    prob.addQuery('state', 'NOT IN', '4');
    prob.query();

    if (!prob.next()) {
      var newProb = new GlideRecord('problem');
      newProb.initialize();
      newProb.short_description = 'Auto-created: Recurring error - ' + errorCode;
      newProb.u_error_code      = errorCode;
      newProb.assignment_group  = current.assignment_group;
      var probId = newProb.insert();
      current.problem_id = probId;
    } else {
      current.problem_id = prob.sys_id;
    }
  }
}
autoCreateProblem();

💡 Pro Tip

This pattern is called "Event Correlation". In enterprise setups, you can also use ServiceNow's built-in Major Incident Management feature for similar outcomes.

Q5. A user orders a laptop from the Service Catalog. The order must go to the manager for approval, then to IT Procurement. How do you set this up?

Service Catalog  |  Intermediate

Use Flow Designer for this multi-stage approval workflow:

  1. Trigger: Service Catalog — when item is ordered
  2. Ask For Approval → Approver = Requester's Manager
  3. If approved → Ask For Approval → Group = IT Procurement
  4. If approved → Create Task assigned to Procurement team
  5. If rejected → Notify requester with rejection reason

Supporting Catalog Client Script to display the manager name on the form:

// Catalog Client Script — Type: onLoad
function onLoad() {
  var userId = g_user.userID;
  var ga = new GlideAjax('UserInfoAjax');
  ga.addParam('sysparm_name', 'getManagerName');
  ga.addParam('sysparm_user_id', userId);
  ga.getXMLAnswer(function(answer) {
    if (answer) {
      g_form.setValue('u_manager_info', answer);
      g_form.setReadOnly('u_manager_info', true);
    }
  });
}

// Script Include: UserInfoAjax
var UserInfoAjax = Class.create();
UserInfoAjax.prototype = Object.extendsObject(AbstractAjaxProcessor, {
  getManagerName: function() {
    var userId = this.getParameter('sysparm_user_id');
    var user   = new GlideRecord('sys_user');
    if (user.get(userId)) {
      var manager = new GlideRecord('sys_user');
      if (manager.get(user.manager)) {
        return manager.getDisplayValue('name');
      }
    }
    return 'No manager assigned';
  },
  type: 'UserInfoAjax'
});

💡 Pro Tip

Always use Flow Designer for new catalog workflows. Workflow Editor is legacy and being phased out. Flow Designer gives better visibility and easier debugging.

Q6. An SLA is not pausing when an incident moves to 'Awaiting User Info' state. How do you troubleshoot and fix this?

SLA Management  |  Advanced

This is one of the most common SLA issues in production. The problem is almost always in the SLA Definition's Pause Conditions.

Troubleshooting Steps:

  1. Open the Incident → click the SLA tab → check if stage shows 'In Progress' instead of 'Paused'
  2. Go to Process Automation → SLA → SLA Definitions → find the relevant SLA
  3. Check the Pause Condition — most common mistake is using the display label instead of the state integer value
// SLA Pause Condition (Script field inside SLA Definition form)
// 'task' refers to the incident record

(function slaCondition(task) {
  // Use the INTEGER value, NOT the display label
  return (task.state == '10'); // 10 = Awaiting User Info
})(task);

// Verify exact state integer values using Scripts - Background:
var inc = new GlideRecord('incident');
inc.addQuery('state', 10);
inc.setLimit(1);
inc.query();
if (inc.next()) {
  gs.info('State 10 = ' + inc.getDisplayValue('state'));
}

💡 Pro Tip

90% of SLA pause issues happen because developers use the display label instead of the actual integer state value. Always verify numeric values using Scripts - Background before writing the SLA condition.

Q7. An incident is raised but the CI linked to it shows the wrong owner. How do you ensure CI data is validated automatically on incident creation?

CMDB & Asset Management  |  Advanced

Use a Business Rule that cross-validates CI operational status and auto-populates the assignment group from the CI's support group:

// Business Rule: Validate CI on Incident creation
// Table: Incident | When: Before Insert/Update | Condition: CI is not empty

(function executeRule(current, previous) {
  if (current.cmdb_ci && !current.cmdb_ci.nil()) {
    var ci = new GlideRecord('cmdb_ci');
    if (ci.get(current.cmdb_ci)) {

      // Warn if CI is not Operational
      if (ci.operational_status != '1') {
        current.work_notes =
          '[CMDB Alert] Linked CI is not Operational. ' +
          'Status: ' + ci.getDisplayValue('operational_status') +
          '. Please verify before proceeding.';
      }

      // Auto-set assignment group from CI support group
      if ((!current.assignment_group || current.assignment_group.nil())
           && ci.support_group) {
        current.assignment_group = ci.support_group;
        current.work_notes += '\nAssignment group auto-set from CI support group.';
      }
    }
  }
})(current, previous);

💡 Pro Tip

Pair this Business Rule with ServiceNow Discovery or ITOM to auto-update CI attributes. Schedule a weekly CMDB Health Dashboard review to catch data quality issues early.

Q8. The helpdesk wants an email sent to the caller every time a work note is added to their incident. How do you configure this?

Notifications & Events  |  Beginner

This is pure configuration — no code needed.

Steps:

  1. Go to System Notification → Email → Notifications → New
  2. Table: Incident
  3. When to send: Record Updated
  4. Condition: Work notes | changes
  5. Who will receive: Caller (select the Caller field as recipient)

Email template using built-in variables:

Hello ${caller_id.first_name},

Your incident ${number} has been updated.

Update Details:
${work_notes}

Current Status : ${state}
Priority       : ${priority}

To add more information, visit the portal or reply to this email.

Thank you,
IT Support Team

💡 Pro Tip

Uncheck 'Send to event creator' so agents don't receive emails for their own updates. Consider a notification digest to prevent email flooding on busy incidents.

Q9. The IT manager wants a dashboard showing open incidents by priority, SLA compliance rate this month, and top 5 categories. How do you build this?

Reporting & Dashboards  |  Intermediate

Build three separate reports in Report Designer, then pin them to a shared Dashboard.

  • Widget 1: Bar Chart | Table: Incident | Filter: State != Resolved/Closed | Group by: Priority
  • Widget 2: Donut | Table: Task SLA | Filter: Planned End = This Month | Has Breached = false
  • Widget 3: Top 5 Categories — via Report Designer or the script below
// Scheduled Script: Top 5 Incident Categories this month

var ga = new GlideAggregate('incident');
ga.addQuery('opened_at', '>=', gs.beginningOfThisMonth());
ga.groupBy('category');
ga.addAggregate('COUNT');
ga.orderByAggregate('COUNT');
ga.setMaxTableCount(5);
ga.query();

var results = [];
while (ga.next()) {
  results.push({
    category : ga.getDisplayValue('category'),
    count    : ga.getAggregate('COUNT')
  });
}
gs.info(JSON.stringify(results));
// Output: [{"category":"Network","count":"45"}, ...]

💡 Pro Tip

For real-time dashboards, use Performance Analytics (PA) instead of standard reports. PA supports trend lines, time-series charts, and auto-refresh. Standard reports are point-in-time snapshots only.

Q10. An incident sits in 'In Progress' for more than 4 hours without any update. The team lead should get an alert. How do you automate this?

Flow Designer & Automation  |  Advanced

Flow Designer approach: Trigger on Record Updated → Wait 4 hours → Check if still In Progress → Send notification to manager.

Alternative — Scheduled Script (runs every 30 minutes):

// Scheduled Script: Alert on stale incidents

(function checkStaleIncidents() {
  var staleTime = new GlideDateTime();
  staleTime.subtract(14400000); // 4 hours in milliseconds

  var inc = new GlideRecord('incident');
  inc.addQuery('state', '2'); // In Progress
  inc.addQuery('sys_updated_on', '<=', staleTime);
  inc.addQuery('u_stale_notified', '!=', 'true'); // Prevent duplicate alerts
  inc.query();

  while (inc.next()) {
    var group = new GlideRecord('sys_user_group');
    if (group.get(inc.assignment_group)) {
      gs.eventQueue(
        'incident.stale.alert',
        inc,
        group.manager,
        inc.number
      );
      inc.u_stale_notified = 'true';
      inc.work_notes = 'Auto-alert sent: Incident stale 4+ hours.';
      inc.update();
    }
  }
})();

💡 Pro Tip

Register the custom event 'incident.stale.alert' in System Policy → Events → Registry, then build a Notification that listens to it. Much cleaner than hardcoding email logic inside the script.

Q11. When an agent selects 'Hardware' as the category on an incident, only hardware-related subcategories should appear. How do you implement this?

Client Scripts & UI  |  Beginner

Use an onChange Client Script on the Category field to clear and re-populate Subcategory options dynamically:

// Client Script: Filter Subcategory based on Category
// Type: onChange | Table: Incident | Field: Category

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

  g_form.setValue('subcategory', '');
  g_form.clearOptions('subcategory');

  var subcategoryMap = {
    'hardware' : ['Laptop', 'Desktop', 'Printer', 'Monitor', 'Keyboard'],
    'software' : ['Installation', 'License', 'Update', 'Crash'],
    'network'  : ['VPN', 'WiFi', 'Ethernet', 'Firewall'],
    'access'   : ['Account Locked', 'Password Reset', 'New Access', 'Remove Access']
  };

  var options = subcategoryMap[newValue.toLowerCase()] || [];
  g_form.addOption('subcategory', '', '--Select Subcategory--');
  options.forEach(function(opt) {
    g_form.addOption('subcategory', opt.toLowerCase().replace(/ /g, '_'), opt);
  });
}

💡 Pro Tip

If subcategory is a Reference field (not a choice list), use GlideAjax + a Script Include. A Client Script alone cannot filter reference fields — use a Dynamic Reference Qualifier on the field definition instead.

Q12. Only members of the 'HR Team' group should see incidents where category is 'HR Confidential'. How do you restrict this using ACLs?

Security & Access Control  |  Advanced

Create two ACLs — one for the record view and one for the list view. Operation: Read | Table: Incident | Condition: Category = HR Confidential. Add this script in the ACL Script field:

// ACL Script: HR Confidential Incident Access
// Applied to: Incident (record) | Operation: Read

(function checkAccess() {
  // Allow HR Team members
  if (gs.getUser().isMemberOf('HR Team')) {
    return true;
  }
  // Allow admins
  if (gs.hasRole('admin')) {
    return true;
  }
  // Allow the caller to see their own incident
  if (current.caller_id == gs.getUserID()) {
    return true;
  }
  // Deny everyone else
  return false;
})();

// IMPORTANT: Create a second identical ACL for the LIST view:
// Table: Incident [list] | Operation: Read | Same script
// Without this, HR Confidential incidents still appear in list views!

💡 Pro Tip

Always create ACLs in pairs — one for the record and one for the list view. Missing the list ACL is the #1 security gap developers miss. Test with a non-admin user using the Impersonate User feature in ServiceNow.

Key Takeaways

  • ✔ Use Assignment Rules before Business Rules for auto-assignment
  • ✔ Business Rules handle server-side logic — before/after insert/update
  • ✔ Flow Designer is the modern replacement for Workflow Editor
  • ✔ SLA pause conditions always use integer state values, not display labels
  • ✔ Always create ACLs in pairs — record view + list view
  • ✔ Client Scripts handle client-side UI; Business Rules handle server-side data
  • ✔ Use GlideAggregate (not GlideRecord) when you only need counts

Post a Comment

0Comments
Post a Comment (0)