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.
// 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:
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')
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
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
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
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
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 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
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
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
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)
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
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.
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.
1---2name: es-toolkit-23description: 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.4---56# es-toolkit Usage Guide78es-toolkit is a modern JavaScript utility library — 2-3x faster and up to 97% smaller than lodash.9It has 100% test coverage, built-in TypeScript types, and supports Node.js 18+, Deno, Bun, and browsers.1011## Import Rules1213ALWAYS use subpath imports for tree-shaking. NEVER import from the top-level barrel.1415```typescript16// CORRECT — subpath imports (tree-shakeable)17import { groupBy } from "es-toolkit/array";18import { debounce } from "es-toolkit/function";19import { pick, omit } from "es-toolkit/object";20import { snakeCase } from "es-toolkit/string";21import { sum } from "es-toolkit/math";22import { isNil } from "es-toolkit/predicate";23import { delay, Mutex, Semaphore } from "es-toolkit/promise";24import { attempt } from "es-toolkit/util";25import { mapKeys } from "es-toolkit/map";26import { keyBy } from "es-toolkit/set";2728// ALSO CORRECT — top-level for classes/quick scripts29import { Mutex, Semaphore } from "es-toolkit";3031// CORRECT — compat layer (lodash-compatible API)32import { get, set, template } from "es-toolkit/compat";3334// WRONG — never do this35import _ from "es-toolkit";36import { groupBy } from "es-toolkit/dist/array";37```3839## Decision Guide4041Before writing any utility function, check this hierarchy:42431. **Native JS/TS** — Use native when it's equally readable and performant44 - `Array.prototype.map/filter/reduce/find/some/every/flat/flatMap`45 - `Object.keys/values/entries/assign/fromEntries`46 - `structuredClone()` for deep cloning (but `cloneDeep` handles edge cases better)47 - `?.` optional chaining instead of `get(obj, 'a.b.c')`48492. **es-toolkit** — Use when native is verbose, error-prone, or missing the operation50 - Array: `groupBy`, `sortBy`, `orderBy`, `uniq`, `uniqBy`, `difference`, `intersection`, `union`, `chunk`, `zip`, `shuffle`, `sample`51 - Object: `pick`, `omit`, `pickBy`, `omitBy`, `merge`, `toMerged`, `cloneDeep`, `invert`52 - String: `camelCase`, `snakeCase`, `kebabCase`, `capitalize`, `pascalCase`, `constantCase`, `deburr`, `escapeRegExp`53 - Function: `debounce`, `throttle`, `once`, `memoize`, `retry`, `flow`, `flowRight`, `noop`54 - Math: `sum`, `sumBy`, `mean`, `meanBy`, `round`, `clamp`, `inRange`, `random`, `randomInt`55 - Predicate: `isNil`, `isNotNil`, `isEqual`, `isEqualWith`, `isEmpty`, `isPlainObject`56 - Promise: `delay`, `timeout`, `Mutex`, `Semaphore`57 - Util: `attempt`, `attemptAsync`, `invariant`58593. **es-toolkit/compat** — Only for lodash migration or when you need exact lodash behavior60 - `get`, `set`, `has`, `template`, `iteratee`, `matches`, `property`61 - Prefer native `?.` over `get()` — compat `get` is slower due to path parsing6263## Quick Reference — Most Used Functions6465### Array6667```typescript68import {69 groupBy,70 sortBy,71 uniqBy,72 difference,73 chunk,74 zip,75 sample,76 countBy,77 flatMapDeep,78 orderBy,79} from "es-toolkit/array";8081groupBy(items, item => item.category); // Record<string, T[]>82sortBy(users, [u => u.age, u => u.name]); // T[] — stable multi-key sort83orderBy(users, ["age", "name"], ["asc", "desc"]); // T[] — with direction control84uniqBy(items, item => item.id); // T[] — unique by key85difference([1, 2, 3], [2, 3]); // [1]86chunk([1, 2, 3, 4, 5], 2); // [[1,2], [3,4], [5]]87zip(["a", "b"], [1, 2]); // [["a",1], ["b",2]]88sample([1, 2, 3]); // random element89countBy([1, 2, 3, 4], n => (n % 2 === 0 ? "even" : "odd")); // { even: 2, odd: 2 }90```9192### Object9394```typescript95import {96 pick,97 omit,98 merge,99 toMerged,100 cloneDeep,101 pickBy,102 omitBy,103 invert,104 clone,105} from "es-toolkit/object";106107pick(user, ["name", "email"]); // { name, email }108omit(user, ["password", "secret"]); // everything except those keys109merge(target, source); // deep merge (MUTATES target)110toMerged(target, source); // deep merge (returns NEW object)111cloneDeep(complexObj); // deep clone (handles Date, RegExp, Map, Set)112pickBy(obj, (value, key) => value != null); // pick by predicate113invert({ a: "1", b: "2" }); // { "1": "a", "2": "b" }114```115116### Function117118```typescript119import {120 debounce,121 throttle,122 once,123 retry,124 flow,125 memoize,126 noop,127 identity,128} from "es-toolkit/function";129130// Debounce with cancel/flush and AbortSignal support131const search = debounce(query => fetchResults(query), 300);132search.cancel();133search.flush();134135// Debounce with leading edge136const leading = debounce(fn, 300, { edges: ["leading"] });137138// Throttle139const onScroll = throttle(handleScroll, 100);140141// Retry with exponential backoff142const data = await retry(fetchData, {143 retries: 5,144 delay: attempt => Math.min(100 * 2 ** attempt, 5000),145 shouldRetry: (err, attempt) => err.status >= 500,146 signal: controller.signal,147});148149// Function composition pipeline150const transform = flow(trim, toLowerCase, split(" "), filterEmpty);151152// Execute only once153const initialize = once(() => expensiveSetup());154```155156### Promise & Concurrency157158```typescript159import { delay, timeout, Mutex, Semaphore } from "es-toolkit/promise";160161await delay(1000); // wait 1 second162const result = await timeout(fetchData(), 5000); // timeout after 5s163164// Mutex — single concurrent task165const mutex = new Mutex();166await mutex.acquire();167try {168 /* critical section */169} finally {170 mutex.release();171}172173// Semaphore — N concurrent tasks174const sem = new Semaphore(3);175await sem.acquire();176try {177 /* max 3 concurrent */178} finally {179 sem.release();180}181```182183### String184185```typescript186import {187 camelCase,188 snakeCase,189 kebabCase,190 pascalCase,191 capitalize,192 constantCase,193 deburr,194 escapeRegExp,195} from "es-toolkit/string";196197camelCase("foo-bar"); // "fooBar"198snakeCase("fooBar"); // "foo_bar"199kebabCase("FooBar"); // "foo-bar"200pascalCase("foo-bar"); // "FooBar"201constantCase("fooBar"); // "FOO_BAR"202capitalize("hello"); // "Hello"203deburr("cafe\u0301"); // "cafe"204escapeRegExp("a.b*c"); // "a\\.b\\*c"205```206207### Math208209```typescript210import {211 sum,212 sumBy,213 mean,214 meanBy,215 round,216 clamp,217 inRange,218 random,219 randomInt,220} from "es-toolkit/math";221222sum([1, 2, 3]); // 6223sumBy(products, p => p.price * p.quantity); // total224round(1.2345, 2); // 1.23225clamp(15, 0, 10); // 10226inRange(5, 1, 10); // true227randomInt(1, 100); // random integer in [1, 100)228```229230### Predicate (Type Guards)231232```typescript233import { isNil, isNotNil, isEqual, isEmpty, isPlainObject } from "es-toolkit/predicate";234235isNil(null); // true — null or undefined236isNotNil(value); // true — narrows type to exclude null/undefined237isEqual({ a: 1 }, { a: 1 }); // true — deep structural equality238isEmpty([]); // true239isPlainObject({}); // true240```241242### Util243244```typescript245import { attempt, attemptAsync, invariant } from "es-toolkit/util";246247// Safe sync execution — returns [error, result] tuple248const [error, result] = attempt(() => JSON.parse(input));249250// Safe async execution251const [error, data] = await attemptAsync(async () => {252 const res = await fetch("/api/data");253 return res.json();254});255256// Invariant — throws if condition is falsy257invariant(user != null, "User must exist");258```259260## Lodash Migration261262For complete lodash migration details, see [references/lodash-migration.md](references/lodash-migration.md).263264Key points:265266- `es-toolkit/compat` provides 100% lodash test compatibility (v1.39.3+)267- Import from `es-toolkit/compat` for drop-in replacement268- Prefer native `es-toolkit` over `es-toolkit/compat` for better performance269- NOT supported: `sortedUniq`, `sortedUniqBy`, `mixin`, `noConflict`, `runInContext`, method chaining (Seq)270271## Full API Catalog272273For the complete list of all functions by category, see [references/api-catalog.md](references/api-catalog.md).