Lodash _.min() method

Beginner
⏱️ 6 min read
📚 Updated: May 2026
🎯 3 Code examples
🚀 3 Try-it labs
Lodash

What you’ll learn

  • How _.min(array) walks the array exactly once via baseExtremum(array, identity, baseLt).
  • Why empty / null / undefined input returns undefined (vs Math.min()'s Infinity).
  • That null, undefined, and NaN elements are silently skipped; Symbol mid-array throws.
  • That _.min takes only one argument—any iteratee you pass is ignored. Use _.minBy() for objects.
  • How strings compare lexicographically and why mixed-type arrays are order-dependent.

Prerequisites

Read _.max() first—same engine (baseExtremum), opposite comparator. Skim the Math hub for the “nullish identity” defaults across the family.

  • JavaScript < operator: string vs string is lexicographic; number vs string coerces to Number and gives NaN when invalid.
  • Nullish defaulting: prefer ?? over || when 0 or empty string are legitimate values.

Overview

The implementation is six lines: (array && array.length) ? baseExtremum(array, identity, baseLt) : undefined. Inside baseExtremum, the running “computed” value is seeded by the first element that passes value != null && value === value && !isSymbol(value). Every subsequent element is compared with baseLt (just value < computed) and replaces the result on a strict less-than.

Single pass, strict <

No sort, no copy. The first occurrence wins on ties because the new value must be strictly smaller.

Friendlier empty than Math.min

_.min([])undefined. Math.min()Infinity. The first is easier to default safely.

No iteratee—use _.minBy

A second argument is silently ignored. For object arrays reach for _.minBy(arr, 'n').

Syntax

javascript
_.min(array)
  • array: the array to scan. Falsy / empty input short-circuits.
  • Returns: the minimum value, or undefined if every element was skipped or the input was empty.
  • No iteratee. _.min(arr, fn) still works but the fn argument is dropped. Use _.minBy() when you need that.
1

Basics — numbers, strings, empty

The bread-and-butter: integer arrays, lexicographic string min, and the empty/falsy → undefined contract. Note the strict <: ties keep the first occurrence.

javascript
import min from "lodash/min";

console.log(min([4, 2, 8, 6]));               // 2  (docs example)
console.log(min([4, 7, 1, 9, 12, 5]));         // 1
console.log(min([-3, -7, -1]));                // -7  (all negatives)
console.log(min([3, 0, 5]));                   // 0  (zero is valid)

// Strings compare lexicographically
console.log(min(["banana", "apple", "cherry"])); // "apple"
console.log(min(["10", "9", "2"]));              // "10"  ('1' < '2' < '9')

// Empty / falsy input
console.log(min([]));                          // undefined
console.log(min(null));                        // undefined
console.log(min(undefined));                   // undefined
Try it Yourself
2

What gets skipped — NaN, nullish, Symbols

The seed-guard inside baseExtremum rejects null, undefined, NaN, and Symbols. The catch: Symbols are only skipped while looking for the first valid seed. A Symbol that shows up after a non-Symbol value falls through to baseLt’s < operator and throws.

javascript
import min from "lodash/min";

console.log(min([4, NaN, 2]));        // 2          (NaN skipped)
console.log(min([NaN, NaN]));         // undefined  (every candidate skipped)
console.log(min([4, null, 2]));       // 2          (null skipped)
console.log(min([4, undefined, 2]));  // 2          (undefined skipped)

// Symbol at the start: skipped during seeding
console.log(min([Symbol("x"), 1, 2]));        // 1

// Symbol after a non-Symbol: throws
try {
  console.log(min([1, Symbol("x"), 2]));
} catch (err) {
  console.log("Threw:", err.message);
  // -> "Cannot convert a Symbol value to a number"
}
Try it Yourself
3

Defaulting safely — ?? not ||

A classic bug: _.min(arr) || default looks fine until the actual minimum is 0 (or ''), and your default replaces a real value. Use ?? or an explicit === undefined check.

javascript
import min from "lodash/min";

const scores = [3, 0, 5];

console.log(min(scores) || -1);    // -1   BUG: real min is 0
console.log(min(scores) ?? -1);    // 0    OK

// Defaulting empty arrays
console.log(min([]) ?? 0);          // 0   (no real min, default kicks in)

// BigInt arrays compare correctly
console.log(min([3n, 1n, 5n]));    // 1n

// Mixed types: order-dependent, NaN-from-coercion kills updates
console.log(min([5, "a", 3]));      // 3   ("a" doesn't beat 5; 3 does)
console.log(min(["a", 5, 3]));      // "a" ('a' seeded first; 5 < 'a' is NaN)
Try it Yourself

📋 _.min vs Math.min

Input_.min(arr)Math.min(...arr)Notes
[4, 2, 8, 6]22Same answer.
[]undefinedInfinityLodash is more defensive; Math.min's identity for ×-spread is Infinity.
[4, NaN, 2]2NaNLodash skips NaN; Math.min poisons.
[3n, 1n, 5n]1nthrowsLodash works on pure BigInts; Math.min can't convert BigInt.
[1, Symbol('x'), 2]throwsthrowsSame root cause: Symbol < n fails.
["b", "a"]"a"NaNLodash supports lexicographic; Math.min coerces to NumberNaN.

Pitfalls to avoid

Defaulting

|| swallows zero

_.min(arr) || 100 turns a legitimate min of 0 into 100. Use ?? or compare to undefined explicitly.

Iteratee trap

Second argument is dropped

_.min([{n:3},{n:1}], 'n') returns {n:3} (the seed), not {n:1}. Use _.minBy when you need an iteratee.

Mixed types

Order-dependent answers

_.min([5, "a", 3]) is 3 but _.min(["a", 5, 3]) is "a". Sanitize input to a single type first.

Symbol

Position matters

Symbol-first arrays work; Symbol-after-a-seed throws. If user data might contain symbols, filter them out: arr.filter(v => typeof v !== 'symbol').

Numeric strings

Lex, not numeric

_.min(["10", "9", "2"]) returns "10", not "2". Cast first: arr.map(Number).

❓ FAQ

It returns undefined—officially documented in the source. This is different from Math.min(), which returns Infinity. Always guard with === undefined or ??, never with || (which would mistakenly replace a real min of 0).
No. _.min is a single-argument function—any second argument is silently ignored. For object arrays use _.minBy(arr, 'price') or _.minBy(arr, o => o.price).
All three are silently skipped during iteration. _.min([4, NaN, 2]) returns 2, _.min([NaN, NaN]) returns undefined (every candidate was skipped). The guard is current != null && current === current inside baseExtremum.
Symbols don't seed the result, but if a Symbol appears AFTER a non-Symbol value, baseLt's < operator throws 'Cannot convert a Symbol value to a number'. Strip Symbols before passing the array if your data might contain them.
Yes—pure BigInt arrays compare correctly because < works on BigInts. Mixed BigInt + Number arrays also work (the < operator allows that cross-type comparison). This is one of the few Math methods that's BigInt-friendly.
Yes. _.min(['banana', 'apple', 'cherry']) returns 'apple'. Note that numeric strings compare lexicographically, so _.min(['10', '9', '2']) is '10', not '2'.

Summary

  • Purpose: single-pass minimum of an array using baseLt (<); skips nullish and NaN values.
  • Remember: empty/null → undefined; no iteratee (use _.minBy); Symbol mid-array throws; strings compare lexicographically.
  • Next: Lodash _.minBy() for the iteratee form, or read the official Lodash docs for _.min.
Did you know?

Don’t reach for || when defaulting _.min(arr)_.min([3, 0, 5]) || -1 returns -1 instead of the real minimum 0. Use ?? (or check === undefined) and your defaults will only fire on truly empty input.

About the author

Mari Selvan M P
Mari Selvan M P 🔗

Developer, cloud engineer, and technical writer

  • Experience 12 years building web and cloud systems
  • Focus Full Stack Development, AWS, and Developer Education

I write practical tutorials so students and working developers can learn by doing—from databases and APIs to deployment on AWS.

6 people found this page helpful