_.once()
beginnerGuarantee a function runs exactly once and returns its cached result forever after.
Implement lodash's _.partial in JavaScript with Symbol placeholders and this forwarding — and nail the partial-vs-curry-vs-bind comparison interviewers always ask.
Implement
partial(fn, ...presetArgs): return a function with the leading arguments pre-filled — plus placeholder support so any positions can be preset, not just the leading ones.
Partial application is the simpler sibling of currying: you choose the argument split once, get back one function, and the next call runs fn — no arity counting, no chains. It's also two-thirds of bind (the argument half, without the this half), which makes "compare these three" the inevitable follow-up. Have the table ready.
const _ = Symbol("placeholder"); // unforgeable — a string '_' collides with real data
function partial(fn, ...presetArgs) {
return function (...laterArgs) {
let n = 0;
// fill placeholders left-to-right with incoming args…
const args = presetArgs.map((arg) =>
arg === _ && n < laterArgs.length ? laterArgs[n++] : arg
);
// …then append whatever's left
args.push(...laterArgs.slice(n));
return fn.apply(this, args); // forward this — partial must not sever it
};
}const greet = (greeting, name, punct) => `${greeting}, ${name}${punct}`;
const hello = partial(greet, "Hello");
hello("Ada", "!"); // "Hello, Ada!" — leading preset
const exclaim = partial(greet, _, _, "!");
exclaim("Hi", "Grace"); // "Hi, Grace!" — trailing preset via holes
const greetAda = partial(greet, _, "Ada");
greetAda("Hey", "?"); // "Hey, Ada?" — middle preset, extras appendThe merge is one pass — fill holes in order, append leftovers. Unlike curry with placeholders, there's no completion rule and no recursion: unfilled holes simply arrive as the Symbol and it's the caller's bug — partial is deliberately dumb, and that simplicity is a feature to point out, not a gap to apologize for.
Specialization of general functions is everywhere; it just usually wears arrow-function clothes:
const log = partial(console.log, "[checkout]"); // tagged logger
const apiFetch = partial(fetchJson, BASE_URL); // client bound to a host
onClick={() => deleteItem(item.id)} // ← this IS partial applicationThat last line is the honest framing for 2026: arrows are inline partial application, and they won because they're readable and typecheck trivially. partial the utility earns its keep when the specialized function is created dynamically or in bulk (mapping over configs to build handler tables). Presenting arrows as the default and partial as the bulk tool is the right taste signal.
partial(fn, 1) | curry(fn)(1) | fn.bind(ctx, 1) | |
|---|---|---|---|
| Returns | one function, split chosen once | a collector; keeps accepting args until arity | one function |
| Next call | always invokes fn | invokes only when arity is reached | always invokes |
this | forwarded from call site | forwarded | fixed to ctx forever |
| Placeholders | yes (this article) | yes (harder version) | no — leading args only |
The this row is the sharpest differentiator: partial preserves dynamic this (our wrapper is a function and applies this through), while bind freezes it. partial(obj.method, x) still needs the method attached to an object at call time or bound first — the lost-this bug applies to partials of methods exactly as it does to raw callbacks.
partial(greet, _, "Ada")() passes the Symbol itself as greeting. Options: leave it (lodash's behavior — garbage in, garbage out), or throw on unfilled holes at call time. Either is defensible stated; silently substituting undefined is not.undefined as a preset — works fine because the sentinel is a Symbol; with an undefined-as-placeholder design it would be indistinguishable — the sentinel-choice reasoning again (same as curry).fn.length of the result — our wrapper reports 0 (rest params); lodash doesn't fix this either, but it breaks downstream arity-dependent tools like curry — a genuinely sneaky interaction worth naming.partial call makes a new function; in React-land that means memo-busting unless wrapped in useMemo/useCallback — the same identity discipline as debounce in React.'_' as the placeholder — a legitimate "_" argument (CSS class, lodash import name!) silently becomes a hole.this forwarding and quietly turns partial into half-broken bind.shift()-based hole filling that mutates shared state across calls — the preset array must be treated as immutable (note the map builds a fresh array every call).partial where an arrow is clearer — library-brain; the reviewer's version of this question is "when would you not use it?"partialRight." — presets fill from the right: prepend laterArgs, append presets (mirror the merge). Lodash ships both; the exercise checks the merge generalizes in your head.bind using partial." — bind(fn, ctx, ...preset) = partial(fn, ...preset) with apply(ctx) instead of apply(this) — plus the new-operator wrinkle that makes real bind harder.Parameters<F> spread); placeholders defeat static typing entirely — knowing that placeholder-partial is a dynamically-typed pattern is the mature answer.Guarantee a function runs exactly once and returns its cached result forever after.
Compose functions left-to-right into a pipeline — the one-liner with a lot of interview depth.
Cache function results by argument key — and discuss cache-key strategy and memory trade-offs.
The harder follow-up: support _ placeholders so arguments can arrive in any order.