lodash
Lodash is a grab-bag of utility functions for arrays, objects, strings, and functions: debounce, cloneDeep, groupBy, merge, get, and about 300 more. It sits in your dependency tree as the standard library JavaScript never shipped. It has been on major version 4 since 2016 and, per the OpenJS Foundation announcement in its own README, is now moving to a Feature-Complete maturity stage: stable and maintained, but no new features are coming.
Still the safest set of utility implementations ever shipped and fine to keep in existing codebases, but for new projects the language plus es-toolkit covers almost everything at a fraction of the weight. Feature-complete status means this is maintenance mode by design, not neglect.
Use it if
- You need battle-tested implementations of debounce, throttle, or deep clone and do not want to hand-roll edge cases like leading/trailing calls or circular references
- You work in a large codebase that already uses it everywhere; consistency beats purity here
- You need deep object operations natives still do not cover well: merge with nested defaults, groupBy/keyBy, orderBy with multiple keys, isEqual structural comparison
- You support older runtimes and want utilities that do not assume the newest ECMAScript features
- You are starting a new project in 2026: modern JS covers a lot of lodash (flat, Object.entries, structuredClone, Array.prototype.at, optional chaining replaces most _.get calls), and es-toolkit or remeda give you the rest smaller and tree-shakeable
- Bundle size matters and your team imports the whole package: the main lodash package is CommonJS and does not tree-shake, so 'import _ from lodash' ships roughly 70 KB minified for the two functions you actually use
- You want a library that will grow with the language: lodash is explicitly feature-complete, so new proposals and patterns will never land in it
- You only need one function: lodash.debounce-style per-method packages exist but are frozen at old versions; copying 20 lines or using a focused package is cleaner
Setup reality
npm install lodash and it just works, no peer dependencies, no config, runs anywhere. The annoyance is bundling: the default package is CommonJS, so bundlers cannot tree-shake it and you must either cherry-pick imports (import debounce from 'lodash/debounce'), switch to lodash-es, or add babel-plugin-lodash. TypeScript types live in a separate @types/lodash install. Teams routinely discover the full library in their bundle months later.
Patterns
Debounce a search inputdebounce-input
import debounce from 'lodash/debounce';
const search = debounce((q) => fetchResults(q), 300);
input.addEventListener('input', (e) => search(e.target.value));
// cancel pending call on teardown
search.cancel();Create the debounced function once, not inside a render/handler, or every call gets a fresh timer and nothing debounces.
Throttle a scroll handlerthrottle-scroll
import throttle from 'lodash/throttle';
const onScroll = throttle(() => updateHeader(window.scrollY), 100, {
leading: true,
trailing: true,
});
window.addEventListener('scroll', onScroll);Default is leading and trailing both true; set trailing: false if a final late call after the user stops scrolling would be wrong.
Deep clone an objectdeep-clone
import cloneDeep from 'lodash/cloneDeep';
const copy = cloneDeep(original);
copy.settings.theme = 'dark'; // original untouchedNative structuredClone covers most cases now but throws on functions and DOM nodes; cloneDeep copies functions by reference instead of throwing.
Read a nested path with a defaultsafe-get
import get from 'lodash/get';
const city = get(user, 'address.city', 'unknown');
// dynamic paths are where get still earns its keep
const val = get(obj, ['rows', idx, 'cells', col], null);For static paths use optional chaining (user?.address?.city ?? 'unknown'); get is only worth it when the path is built at runtime.
Group an array of objects by a fieldgroup-by-key
import groupBy from 'lodash/groupBy';
const byStatus = groupBy(orders, 'status');
// { pending: [...], shipped: [...] }
const byYear = groupBy(posts, (p) => p.date.slice(0, 4));Native Object.groupBy exists in Node 21+ and modern browsers; lodash groupBy is the drop-in for anything older.
Turn an array into a lookup mapindex-by-key
import keyBy from 'lodash/keyBy';
const usersById = keyBy(users, 'id');
const user = usersById[42];Later duplicates overwrite earlier ones silently; use groupBy if keys can repeat and you need all of them.
Deep merge config with defaultsdeep-merge
import merge from 'lodash/merge';
const config = merge({}, defaults, fileConfig, cliOverrides);merge mutates its first argument; always pass {} first. Arrays are merged index-by-index, which surprises almost everyone.
Structural equality checkdeep-equal
import isEqual from 'lodash/isEqual';
if (!isEqual(prevFilters, nextFilters)) {
refetch(nextFilters);
}Handles nested objects, arrays, dates, and regexes; it is O(n) over the whole structure, so avoid it in hot paths on large objects.
Split an array into batcheschunk-array
import chunk from 'lodash/chunk';
for (const batch of chunk(ids, 100)) {
await api.bulkFetch(batch);
}The last chunk is whatever remains and can be shorter than the batch size.
Remove duplicates by a fielddedupe-by-key
import uniqBy from 'lodash/uniqBy';
const unique = uniqBy(contacts, 'email');
const byLower = uniqBy(contacts, (c) => c.email.toLowerCase());First occurrence wins; sort first if you want the newest record kept.
Whitelist or drop object fieldspick-omit-fields
import pick from 'lodash/pick';
import omit from 'lodash/omit';
const publicUser = pick(user, ['id', 'name', 'avatar']);
const sansToken = omit(session, ['accessToken', 'refreshToken']);Prefer pick for API responses: an allowlist stays safe when new sensitive fields are added later, omit does not.
Sort by multiple keys with directionmulti-key-sort
import orderBy from 'lodash/orderBy';
const sorted = orderBy(users, ['role', 'createdAt'], ['asc', 'desc']);Returns a new array (sortBy/orderBy never mutate), and the sort is stable.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| es-toolkit | npm | New code that wants the same utility names, smaller and faster, with native ESM and tree-shaking |
| remeda | npm | TypeScript-first projects that want data-last pipe-friendly utilities with strong type inference |
| lodash-es | npm | You want lodash exactly as-is but as ES modules so your bundler can tree-shake it |