Overview
Prefer native JavaScript over Lodash for new projects. Modern JavaScript (ES2020+) covers array manipulation, object transformation, and most utility patterns that Lodash was written to fill in 2012. Import Lodash only when you need its specific advantages: _.cloneDeep, _.merge, _.debounce, or _.throttle. Even then, import the specific function, not the full library, to avoid adding 70 KB to your bundle. See general-principles for the general rule on dependencies.
When native JS wins
Native JavaScript covers most utility needs in 2026.
- Array methods:
Array.from,Array.prototype.flat,flatMap,findIndex,at(-1)cover the patterns that_.flatten,_.compact,_.find, and_.lastonce solved. - Object handling:
Object.entries,Object.fromEntries,structuredClone, and spread syntax cover most_.pick,_.omit,_.mapValuesuse cases. structuredClone(Node 17+, all modern browsers): deep clones any structured-clonable value without a dependency. Replaces_.cloneDeepfor objects without functions or class instances.- Optional chaining and nullish coalescing:
a?.b?.c ?? fallbackreplaces_.get(a, 'b.c', fallback)with zero overhead. Promise.all,Promise.allSettled: covers_.chunk-and-map patterns for parallel async work.- Bundle impact: removing a full Lodash import saves 70 KB (unparsed); even cherry-picked imports add 2 to 5 KB per utility. Native has zero bundle cost.
// Native equivalents
const flat = arr.flat(Infinity); // _.flattenDeep
const last = arr.at(-1); // _.last
const clone = structuredClone(obj); // _.cloneDeep (for plain objects)
const val = a?.b?.c ?? "default"; // _.get with default
const grouped = Object.groupBy(arr, (x) => x.key); // _.groupBy (ES2024)When Lodash wins
Lodash is justified in four concrete cases.
_.cloneDeepfor non-structured-clonable values: objects with methods, class instances, circular references, or Symbols.structuredClonethrows on these;_.cloneDeephandles them._.mergefor deep recursive merge:Object.assignand spread are shallow._.mergerecursively merges nested objects without replacing them. Common in config systems._.debounceand_.throttle: the native equivalent requires hand-rolling a closure withsetTimeout. Lodash’s implementation handles leading/trailing edges and cancellation correctly. Small lodash-debounce-style packages exist as alternatives.- Legacy codebase with heavy Lodash usage and no time budget for refactoring: stay on it. The incremental migration approach is fine; replace calls as you touch files.
When importing specific Lodash functions, use the per-method package or named import from lodash-es to allow tree-shaking.
// Tree-shakeable import
import { debounce } from "lodash-es";
import cloneDeep from "lodash/cloneDeep";Trade-offs at a glance
| Dimension | Lodash | Native JS |
|---|---|---|
| Bundle size | 70 KB full; 1 to 5 KB per cherry-picked fn | Zero |
| Deep clone | _.cloneDeep (handles all types) | structuredClone (structured-clonable only) |
| Deep merge | _.merge | No built-in; spread is shallow |
| Debounce/throttle | Battle-tested, cancelable | Hand-rolled or separate package |
| Array utilities | Broad; many legacy helpers | Modern methods cover 90% of cases |
| Null-safe access | _.get with default | Optional chaining + nullish coalescing |
| TypeScript types | @types/lodash required | Native |
| Maintenance | Stable; infrequent releases | N/A |
| Learnability | Extra API surface for new contributors | Standard JS; universal knowledge |
| Browser support target | IE11 safe historically | ES2020+ required for modern methods |
Migration cost
Lodash to native migration is file-by-file and low-risk.
- Use
eslint-plugin-you-dont-need-lodash-underscoreto identify functions with native equivalents. It reports and auto-fixes many cases. - Replace
_.get(obj, path, default)with optional chaining and??. Replace_.flattenwith.flat(). Replace_.uniqwith[...new Set(arr)]. - Keep
_.cloneDeep,_.merge,_.debounce, and_.throttleuntil you have specific replacements verified. - Remove
lodashfrompackage.jsononce all usages are gone and tests pass. - Effort: one engineer-day per 100 Lodash callsites, depending on complexity.
Recommendation
- New project: no Lodash. Start with native. Add
lodash-esonly when_.cloneDeep,_.merge,_.debounce, or_.throttleare genuinely needed. - Existing project with Lodash: migrate incrementally as you touch files. Prioritize removing
import _ from 'lodash'(full import) first; it has the largest bundle impact. - Bundle-sensitive app (PWA, mobile web): audit with a bundle analyzer and remove Lodash early. See vite-vs-webpack for analyzer tooling.