---
title: "Lodash vs Native JS"
slug: "lodash-vs-native"
category: "comparisons"
tags: ["comparisons", "lodash", "javascript", "utilities", "bundle-size", "modern-js"]
status: "stable"
last_updated: 2026-05-14
summary: "Use native JavaScript for new projects; add Lodash only when deep clone, deep merge, or debounce utilities are needed and bundle size is not a concern."
related: ["[[comparisons/typescript-vs-javascript]]", "[[comparisons/swc-vs-babel]]", "[[coding/general-principles]]", "[[coding/typescript]]", "[[comparisons/eslint-vs-biome]]", "[[frontend/index]]", "[[comparisons/vite-vs-webpack]]"]
---

## 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 [[coding/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 `_.last` once solved.
- Object handling: `Object.entries`, `Object.fromEntries`, `structuredClone`, and spread syntax cover most `_.pick`, `_.omit`, `_.mapValues` use cases.
- `structuredClone` (Node 17+, all modern browsers): deep clones any structured-clonable value without a dependency. Replaces `_.cloneDeep` for objects without functions or class instances.
- Optional chaining and nullish coalescing: `a?.b?.c ?? fallback` replaces `_.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.

```ts
// 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.

- `_.cloneDeep` for non-structured-clonable values: objects with methods, class instances, circular references, or Symbols. `structuredClone` throws on these; `_.cloneDeep` handles them.
- `_.merge` for deep recursive merge: `Object.assign` and spread are shallow. `_.merge` recursively merges nested objects without replacing them. Common in config systems.
- `_.debounce` and `_.throttle`: the native equivalent requires hand-rolling a closure with `setTimeout`. 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.

```ts
// 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-underscore` to identify functions with native equivalents. It reports and auto-fixes many cases.
- Replace `_.get(obj, path, default)` with optional chaining and `??`. Replace `_.flatten` with `.flat()`. Replace `_.uniq` with `[...new Set(arr)]`.
- Keep `_.cloneDeep`, `_.merge`, `_.debounce`, and `_.throttle` until you have specific replacements verified.
- Remove `lodash` from `package.json` once 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-es` only when `_.cloneDeep`, `_.merge`, `_.debounce`, or `_.throttle` are 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 [[comparisons/vite-vs-webpack]] for analyzer tooling.

## Related

- [[comparisons/typescript-vs-javascript]]
- [[comparisons/swc-vs-babel]]
- [[coding/general-principles]]
- [[coding/typescript]]
- [[comparisons/eslint-vs-biome]]
- [[frontend/index]]
- [[comparisons/vite-vs-webpack]]
