# Es Toolkit

> es-toolkit utility library guide for modern JavaScript/TypeScript. Use when: (1) Writing utility functions (array manipulation, object transforms, string conversion, math operations, type checks), (2) Migrating from lodash, (3) Needing debounce/throttle/retry, (4) Working with async primitives (Mutex, Semaphore, delay), (5) Any code that could use es-toolkit instead of custom implementation. Triggers on: es-toolkit, lodash, utility function, debounce, throttle, groupBy, pick, omit, merge, cloneDeep, snakeCase, camelCase, isNil, isEqual, retry, Mutex, Semaphore, attempt. Do NOT use for: general JavaScript questions unrelated to utilities, or React/framework-specific code.

- Skill: `pedronauck/es-toolkit-2` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add pedronauck/es-toolkit-2`
- Raw SKILL.md: https://api.skillmd.com/api/skills/pedronauck/es-toolkit-2/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: pedronauck (https://skillmd.com/u/pedronauck)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/pedronauck/es-toolkit-2

---


# es-toolkit Usage Guide

es-toolkit is a modern JavaScript utility library — 2-3x faster and up to 97% smaller than lodash.
It has 100% test coverage, built-in TypeScript types, and supports Node.js 18+, Deno, Bun, and browsers.

## Import Rules

ALWAYS use subpath imports for tree-shaking. NEVER import from the top-level barrel.

```typescript
// CORRECT — subpath imports (tree-shakeable)
import { groupBy } from "es-toolkit/array";
import { debounce } from "es-toolkit/function";
import { pick, omit } from "es-toolkit/object";
import { snakeCase } from "es-toolkit/string";
import { sum } from "es-toolkit/math";
import { isNil } from "es-toolkit/predicate";
import { delay, Mutex, Semaphore } from "es-toolkit/promise";
import { attempt } from "es-toolkit/util";
import { mapKeys } from "es-toolkit/map";
import { keyBy } from "es-toolkit/set";

// ALSO CORRECT — top-level for classes/quick scripts
import { Mutex, Semaphore } from "es-toolkit";

// CORRECT — compat layer (lodash-compatible API)
import { get, set, template } from "es-toolkit/compat";

// WRONG — never do this
import _ from "es-toolkit";
import { groupBy } from "es-toolkit/dist/array";
```

## Decision Guide

Before writing any utility function, check this hierarchy:

1. **Native JS/TS** — Use native when it's equally readable and performant
   - `Array.prototype.map/filter/reduce/find/some/every/flat/flatMap`
   - `Object.keys/values/entries/assign/fromEntries`
   - `structuredClone()` for deep cloning (but `cloneDeep` handles edge cases better)
   - `?.` optional chaining instead of `get(obj, 'a.b.c')`

2. **es-toolkit** — Use when native is verbose, error-prone, or missing the operation
   - Array: `groupBy`, `sortBy`, `orderBy`, `uniq`, `uniqBy`, `difference`, `intersection`, `union`, `chunk`, `zip`, `shuffle`, `sample`
   - Object: `pick`, `omit`, `pickBy`, `omitBy`, `merge`, `toMerged`, `cloneDeep`, `invert`
   - String: `camelCase`, `snakeCase`, `kebabCase`, `capitalize`, `pascalCase`, `constantCase`, `deburr`, `escapeRegExp`
   - Function: `debounce`, `throttle`, `once`, `memoize`, `retry`, `flow`, `flowRight`, `noop`
   - Math: `sum`, `sumBy`, `mean`, `meanBy`, `round`, `clamp`, `inRange`, `random`, `randomInt`
   - Predicate: `isNil`, `isNotNil`, `isEqual`, `isEqualWith`, `isEmpty`, `isPlainObject`
   - Promise: `delay`, `timeout`, `Mutex`, `Semaphore`
   - Util: `attempt`, `attemptAsync`, `invariant`

3. **es-toolkit/compat** — Only for lodash migration or when you need exact lodash behavior
   - `get`, `set`, `has`, `template`, `iteratee`, `matches`, `property`
   - Prefer native `?.` over `get()` — compat `get` is slower due to path parsing

## Quick Reference — Most Used Functions

### Array

```typescript
import {
  groupBy,
  sortBy,
  uniqBy,
  difference,
  chunk,
  zip,
  sample,
  countBy,
  flatMapDeep,
  orderBy,
} from "es-toolkit/array";

groupBy(items, item => item.category); // Record<string, T[]>
sortBy(users, [u => u.age, u => u.name]); // T[] — stable multi-key sort
orderBy(users, ["age", "name"], ["asc", "desc"]); // T[] — with direction control
uniqBy(items, item => item.id); // T[] — unique by key
difference([1, 2, 3], [2, 3]); // [1]
chunk([1, 2, 3, 4, 5], 2); // [[1,2], [3,4], [5]]
zip(["a", "b"], [1, 2]); // [["a",1], ["b",2]]
sample([1, 2, 3]); // random element
countBy([1, 2, 3, 4], n => (n % 2 === 0 ? "even" : "odd")); // { even: 2, odd: 2 }
```

### Object

```typescript
import {
  pick,
  omit,
  merge,
  toMerged,
  cloneDeep,
  pickBy,
  omitBy,
  invert,
  clone,
} from "es-toolkit/object";

pick(user, ["name", "email"]); // { name, email }
omit(user, ["password", "secret"]); // everything except those keys
merge(target, source); // deep merge (MUTATES target)
toMerged(target, source); // deep merge (returns NEW object)
cloneDeep(complexObj); // deep clone (handles Date, RegExp, Map, Set)
pickBy(obj, (value, key) => value != null); // pick by predicate
invert({ a: "1", b: "2" }); // { "1": "a", "2": "b" }
```

### Function

```typescript
import {
  debounce,
  throttle,
  once,
  retry,
  flow,
  memoize,
  noop,
  identity,
} from "es-toolkit/function";

// Debounce with cancel/flush and AbortSignal support
const search = debounce(query => fetchResults(query), 300);
search.cancel();
search.flush();

// Debounce with leading edge
const leading = debounce(fn, 300, { edges: ["leading"] });

// Throttle
const onScroll = throttle(handleScroll, 100);

// Retry with exponential backoff
const data = await retry(fetchData, {
  retries: 5,
  delay: attempt => Math.min(100 * 2 ** attempt, 5000),
  shouldRetry: (err, attempt) => err.status >= 500,
  signal: controller.signal,
});

// Function composition pipeline
const transform = flow(trim, toLowerCase, split(" "), filterEmpty);

// Execute only once
const initialize = once(() => expensiveSetup());
```

### Promise & Concurrency

```typescript
import { delay, timeout, Mutex, Semaphore } from "es-toolkit/promise";

await delay(1000); // wait 1 second
const result = await timeout(fetchData(), 5000); // timeout after 5s

// Mutex — single concurrent task
const mutex = new Mutex();
await mutex.acquire();
try {
  /* critical section */
} finally {
  mutex.release();
}

// Semaphore — N concurrent tasks
const sem = new Semaphore(3);
await sem.acquire();
try {
  /* max 3 concurrent */
} finally {
  sem.release();
}
```

### String

```typescript
import {
  camelCase,
  snakeCase,
  kebabCase,
  pascalCase,
  capitalize,
  constantCase,
  deburr,
  escapeRegExp,
} from "es-toolkit/string";

camelCase("foo-bar"); // "fooBar"
snakeCase("fooBar"); // "foo_bar"
kebabCase("FooBar"); // "foo-bar"
pascalCase("foo-bar"); // "FooBar"
constantCase("fooBar"); // "FOO_BAR"
capitalize("hello"); // "Hello"
deburr("cafe\u0301"); // "cafe"
escapeRegExp("a.b*c"); // "a\\.b\\*c"
```

### Math

```typescript
import {
  sum,
  sumBy,
  mean,
  meanBy,
  round,
  clamp,
  inRange,
  random,
  randomInt,
} from "es-toolkit/math";

sum([1, 2, 3]); // 6
sumBy(products, p => p.price * p.quantity); // total
round(1.2345, 2); // 1.23
clamp(15, 0, 10); // 10
inRange(5, 1, 10); // true
randomInt(1, 100); // random integer in [1, 100)
```

### Predicate (Type Guards)

```typescript
import { isNil, isNotNil, isEqual, isEmpty, isPlainObject } from "es-toolkit/predicate";

isNil(null); // true — null or undefined
isNotNil(value); // true — narrows type to exclude null/undefined
isEqual({ a: 1 }, { a: 1 }); // true — deep structural equality
isEmpty([]); // true
isPlainObject({}); // true
```

### Util

```typescript
import { attempt, attemptAsync, invariant } from "es-toolkit/util";

// Safe sync execution — returns [error, result] tuple
const [error, result] = attempt(() => JSON.parse(input));

// Safe async execution
const [error, data] = await attemptAsync(async () => {
  const res = await fetch("/api/data");
  return res.json();
});

// Invariant — throws if condition is falsy
invariant(user != null, "User must exist");
```

## Lodash Migration

For complete lodash migration details, see [references/lodash-migration.md](references/lodash-migration.md).

Key points:

- `es-toolkit/compat` provides 100% lodash test compatibility (v1.39.3+)
- Import from `es-toolkit/compat` for drop-in replacement
- Prefer native `es-toolkit` over `es-toolkit/compat` for better performance
- NOT supported: `sortedUniq`, `sortedUniqBy`, `mixin`, `noConflict`, `runInContext`, method chaining (Seq)

## Full API Catalog

For the complete list of all functions by category, see [references/api-catalog.md](references/api-catalog.md).

