Lodash clamp Lower Bound Greater Than Upper Bound

This article explains the behavior of the _.clamp function in the Lodash JavaScript library when an inverted range is passed—specifically when the lower bound is greater than the upper bound. You will learn the underlying implementation details, why the function produces an unexpected result rather than throwing an error, and how to safely handle inverted boundaries in your applications.

The Default Syntax of Lodash _.clamp

Lodash provides _.clamp to restrict a number within an inclusive lower and upper range:

_.clamp(number, [lower], upper)

Under normal usage, passing _.clamp(15, 0, 10) returns 10, and _.clamp(-5, 0, 10) returns 0. However, Lodash does not validate whether lower <= upper.

What Happens When Lower > Upper

When the lower argument is strictly greater than the upper argument, _.clamp always returns the lower boundary, regardless of the input number.

Consider the following examples:

const _ = require('lodash');

// lower = 10, upper = 5 (lower > upper)
console.log(_.clamp(2, 10, 5));  // Output: 10
console.log(_.clamp(7, 10, 5));  // Output: 10
console.log(_.clamp(12, 10, 5)); // Output: 10

Even if the value being clamped is smaller than both bounds, between both bounds, or larger than both bounds, the result is unconditionally 10.

Why This Happens: The Internal Implementation

The behavior is directly caused by the order of comparison operations in Lodash's source code. The core logic of _.clamp executes as follows:

function clamp(number, lower, upper) {
  if (upper === undefined) {
    upper = lower;
    lower = undefined;
  }
  if (upper !== undefined) {
    number = number <= upper ? number : upper;
  }
  if (lower !== undefined) {
    number = number >= lower ? number : lower;
  }
  return number;
}

When evaluated sequentially:

  1. Upper Bound Check: The function checks number <= upper. If number exceeds upper, it is replaced with upper. At this stage, number is guaranteed to be less than or equal to upper.
  2. Lower Bound Check: The function checks number >= lower. Because lower is greater than upper, and number is already less than or equal to upper, number is mathematically guaranteed to be strictly less than lower.
  3. The Final Assignment: The condition number >= lower consistently evaluates to false, causing the ternary operator to return lower.

Because no check exists to swap the arguments or throw an exception, the function silently fails to clamp correctly and resolves to the lower boundary every time.

How to Prevent Errors With Inverted Bounds

If your input ranges are dynamic and might arrive out of order, normalize them before passing them to _.clamp:

function safeClamp(number, boundA, boundB) {
  const min = Math.min(boundA, boundB);
  const max = Math.max(boundA, boundB);
  return _.clamp(number, min, max);
}

console.log(safeClamp(7, 10, 5)); // Output: 7
console.log(safeClamp(2, 10, 5)); // Output: 5
console.log(safeClamp(12, 10, 5)); // Output: 10

Using Math.min and Math.max guarantees that boundaries are placed in the correct order, avoiding silent logical bugs in your code.