cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

Designing good APIs

Design functions, classes, and modules that are easy to call correctly with options objects, clear naming, fluent APIs, and graceful deprecations.

By the end, you can
  • 01
    Design call sites that explain themselvesReplace long positional lists and boolean flags with named options, clear defaults, and separate functions when the meaning matters.
  • 02
    Keep public surfaces predictableUse consistent naming, argument order, return shapes, async behavior, and helpful errors across a module.
  • 03
    Evolve APIs without surprising usersAccept documented inputs, warn about deprecated calls once, keep aliases temporarily, and plan semver-major removals.

APIs are promises to callers

In this lesson, an API is the public surface of your JavaScript functions, classes, and modules: the names, arguments, return values, errors, and migration rules other developers depend on. It is not only an HTTP endpoint.

Plain definition

A good JavaScript API is easy to use correctly and hard to misuse. It follows the principle of least surprise: call sites read clearly, behavior is consistent with the platform, and mistakes fail with helpful messages.

The platform gives you models to mirror. addEventListener(type, listener, options) takes required values first and an options object last. fetch(url, init) does the same. Array methods use predictable verbs like map, filter, and find. Your APIs should feel as unsurprising as those built-ins.

Real-life analogyA well-labeled control panel

Imagine a control panel in a workshop. Every switch has a label, similar switches point in the same direction, and old controls get a warning sticker before they are removed. A good API does the same for code.

In real life: Labeled switches
In JavaScript: Named options such as { includeTax: true }
In real life: Same direction for every dial
In JavaScript: Consistent argument order and return shapes
In real life: Warning sticker before removal
In JavaScript: Deprecation warning, alias, changelog, and semver major
In real life: Guard rails around dangerous buttons
In JavaScript: TypeError, RangeError, and validation near the boundary

Where the analogy stops: Real control panels cannot accept both arrays and iterables, and they do not evolve with semver. The analogy stops at labeling and predictable operation.

This lesson builds on functions basics, parameters, destructuring, and coding style. Later sections connect to iteration protocols, custom errors, creational patterns, types for JavaScript, and npm semver.

Options objects make call sites readable

STEP THROUGH

Long positional lists work for small internal helpers, but public APIs tend to grow. Once a function has optional flags, numbers, and strings, callers need names at the call site. Put required arguments first, then gather optional choices into one object.

Before: a positional API that works but hides meaningPop out in the code editor (opens in a new tab)JavaScript
function formatPrice(amount, currency, locale, includeTax, taxRate) {  const taxed = includeTax ? amount * (1 + taxRate) : amount;  return new Intl.NumberFormat(locale, {    style: "currency",    currency,  }).format(taxed);} console.log(formatPrice(19.99, "USD", "en-US", true, 0.08));console.log(formatPrice(19.99, "EUR", "en-US", false, 0.08));

Line 1 says the function needs five positional values. Line 9 runs, but the reader has to remember that true means “include tax” and 0.08 means the tax rate. The second call repeats a tax rate that is ignored because includeTax is false.

After: required value plus named optionsPop out in the code editor (opens in a new tab)JavaScript
const defaultFormatPriceOptions = {  currency: "USD",  locale: "en-US",  includeTax: false,  taxRate: 0,}; const formatPriceOptionNames = ["currency", "locale", "includeTax", "taxRate"]; function normalizeFormatPriceOptions(options = {}, warn = console.warn) {  if (options === null || typeof options !== "object" || Array.isArray(options)) {    throw new TypeError("formatPrice options must be an object");  }  const unknown = Object.keys(options).filter(    (name) => !formatPriceOptionNames.includes(name)  );  if (unknown.length > 0) {    warn("Unknown formatPrice option: " + unknown.join(", "));  }  const settings = { ...defaultFormatPriceOptions, ...options };  if (!/^[A-Z]{3}$/.test(settings.currency)) {    throw new TypeError("currency must be a three-letter code");  }  if (typeof settings.locale !== "string") {    throw new TypeError("locale must be a string");  }  if (typeof settings.includeTax !== "boolean") {    throw new TypeError("includeTax must be a boolean");  }  if (typeof settings.taxRate !== "number" || settings.taxRate < 0) {    throw new RangeError("taxRate must be zero or greater");  }  return settings;} function formatPrice(amount, options = {}) {  if (typeof amount !== "number" || !Number.isFinite(amount)) {    throw new TypeError("amount must be a finite number");  }  const settings = normalizeFormatPriceOptions(options);  const taxed = settings.includeTax ? amount * (1 + settings.taxRate) : amount;  return new Intl.NumberFormat(settings.locale, {    style: "currency",    currency: settings.currency,  }).format(taxed);} console.log(formatPrice(19.99, {  includeTax: true,  taxRate: 0.08,  coupon: "FALL",}));console.log(formatPrice(19.99, { currency: "EUR" }));

Line 1 centralizes defaults. Line 8 lists the supported option names. Lines 10 through 20 normalize the incoming object: reject non-objects, warn about unknown keys, and merge defaults with caller choices. Lines 21 through 31 throw TypeError or RangeError near the boundary, before formatting work begins. The call on lines 48 through 52 names the optional choices and shows what a typo warning looks like.

Call-site readability comparison
Long positional APIPop out in the code editor (opens in a new tab)JavaScript
function formatPrice(amount, currency, locale, includeTax, taxRate) {  const taxed = includeTax ? amount * (1 + taxRate) : amount;  return new Intl.NumberFormat(locale, {    style: "currency",    currency,  }).format(taxed);} console.log(formatPrice(19.99, "USD", "en-US", true, 0.08));console.log(formatPrice(19.99, "EUR", "en-US", false, 0.08));
Observed outputpositional
console 1$21.59
console 2€19.99
Try it yourself

The positional call runs, but the reader must remember what true and 0.08 mean. Adding a new optional choice would change the parameter list.

Both examples use real formatting. The difference is how much the call site tells the next developer.
Options object normalization replay
Step 0 of 12Ready
Your turn: follow the blue line

Good APIs normalize inputs near the boundary: merge defaults, warn about unknown options, validate types, then do the real work.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
  currency: "USD",  locale: "en-US",  includeTax: false,  taxRate: 0,}; const formatPriceOptionNames = ["currency", "locale", "includeTax", "taxRate"]; function normalizeFormatPriceOptions(options = {}, warn = console.warn) {  if (options === null || typeof options !== "object" || Array.isArray(options)) {    throw new TypeError("formatPrice options must be an object");  }  const unknown = Object.keys(options).filter(    (name) => !formatPriceOptionNames.includes(name)  );  if (unknown.length > 0) {    warn("Unknown formatPrice option: " + unknown.join(", "));  }  const settings = { ...defaultFormatPriceOptions, ...options };  if (!/^[A-Z]{3}$/.test(settings.currency)) {    throw new TypeError("currency must be a three-letter code");  }  if (typeof settings.locale !== "string") {    throw new TypeError("locale must be a string");  }  if (typeof settings.includeTax !== "boolean") {    throw new TypeError("includeTax must be a boolean");  }  if (typeof settings.taxRate !== "number" || settings.taxRate < 0) {    throw new RangeError("taxRate must be zero or greater");  }  return settings;} function formatPrice(amount, options = {}) {  if (typeof amount !== "number" || !Number.isFinite(amount)) {    throw new TypeError("amount must be a finite number");  }  const settings = normalizeFormatPriceOptions(options);  const taxed = settings.includeTax ? amount * (1 + settings.taxRate) : amount;  return new Intl.NumberFormat(settings.locale, {    style: "currency",    currency: settings.currency,  }).format(taxed);} console.log(formatPrice(19.99, {  includeTax: true,  taxRate: 0.08,  coupon: "FALL",}));console.log(formatPrice(19.99, { currency: "EUR" }));
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
Required versus optional

Do not hide required data inside an options object just to be fashionable. A good shape is often formatPrice(amount, options): the required thing comes first, and optional, extensible behavior lives in the object.

Names and conventions reduce surprise

CONVENTIONS

Names teach the contract before documentation is opened. Use verbs for functions, nouns for values, is/has/can for booleans, plural names for collections, and consistent casing. Avoid abbreviations in public APIs because they are hard to search and easy to misunderstand.

Names should describe the kind of workJavaScript
// Names tell callers what kind of work happens.const userApi = {  getCachedUser(id) {    return cache.get(id);  },  async fetchUser(id) {    return fetch("/api/users/" + id).then((response) => response.json());  },  async loadUser(id) {    return this.getCachedUser(id) ?? this.fetchUser(id);  },  hasUser(id) {    return cache.has(id);  },};

getCachedUser implies a synchronous lookup. fetchUser implies network work and a promise. loadUser can combine cache and network if it is documented to always return a promise or always be synchronous. Do not sometimes return a value and sometimes a promise; that “Zalgo” behavior makes callers guess whether to use await.

Naming conventions that help callers predict behavior
AreaPreferAvoid
Function namesUse verbs: formatPrice, createToast, parsePage.Avoid vague nouns like price() when a function performs work.
BooleansUse is, has, can, or should: isOpen, hasUser, shouldRetry.Avoid flag, status, or mode when the value is true/false.
CollectionsPrefer plurals: users, items, selectedIds.A singular name for an array makes call sites lie.
Async semanticsLet fetchUser always return a promise; let getCachedUser be synchronous.Do not sometimes return a value and sometimes a promise.
Return shapesReturn the same shape for success; throw or return errors consistently.A module that mixes exceptions, null, and { error } surprises callers.
Throw consistently with helpful built-in error typesPop out in the code editor (opens in a new tab)JavaScript
function parsePage(value) {  const page = Number(value);  if (!Number.isInteger(page)) {    throw new TypeError("page must be an integer");  }  if (page < 1) {    throw new RangeError("page must be at least 1");  }  return page;} for (const input of ["2", "0"]) {  try {    console.log(parsePage(input));  } catch (error) {    console.log(error.name + ": " + error.message);  }}

Use one error strategy per public surface. If a parser throws, make all invalid inputs throw helpful errors. Use TypeError when the type is wrong and RangeError when the type is right but the value is outside the accepted range. If a module returns { ok, error } instead, do that consistently everywhere.

Method chaining is a tool, not a default

FLUENT APIs

A fluent API returns this or a new immutable object so callers can chain methods. It reads well when each call is a construction step. It hurts when each step hides side effects, swallows errors, or makes intermediate values hard to inspect.

A small fluent request builderPop out in the code editor (opens in a new tab)JavaScript
function createRequest(path) {  const params = new URLSearchParams();  return {    query(name, value) {      params.set(name, String(value));      return this;    },    build() {      const query = params.toString();      return query ? path + "?" + query : path;    },  };} const url = createRequest("/search")  .query("q", "api design")  .query("page", 2)  .build();console.log(url);

Line 2 stores request parameters. Line 4 accepts one named value, line 6 returns the same builder, and line 8 creates the final URL. This is close to the builder pattern from creational patterns. If you do not need repeated steps or validation, an options object is usually simpler.

Immutable fluent APIs

Returning this mutates one builder. Returning a new object at each step can make chains easier to reason about in state-heavy code, but it costs more allocations. Pick the behavior deliberately and document it.

Avoid boolean traps

CHANGE INPUTS

A boolean trap happens when a call like render(el, true) or setVisible(true, false) makes the reader memorize what each literal means. The boolean value is not the problem; the unnamed literal at the call site is.

Boolean trap redesign
Boolean trap before and afterPop out in the code editor (opens in a new tab)JavaScript
function setVisible(element, visible, animate) {  const state = visible ? "shown" : "hidden";  const motion = animate ? "animated" : "instant";  return element + ":" + state + ":" + motion;} console.log(setVisible("toast", true, false)); function show(element, { animate = true } = {}) {  return setVisible(element, true, animate);}function hide(element, { animate = true } = {}) {  return setVisible(element, false, animate);} console.log(show("toast", { animate: false }));console.log(hide("menu"));
Selected resulttrap
setVisibletoast:shown:instant
Try it yourself

setVisible("toast", true, false) works, but the call hides two decisions. Is false animation, persistence, focus, or something else?

A boolean is fine when it is stored in a named variable. It is a trap when callers must remember what a literal means.

Replace boolean traps with named options, separate functions, or named variables. A call like show("toast", { animate: false }) explains itself. If two modes are genuinely different behaviors, use two verbs: show and hide, or renderStatic and hydrate.

Document arrays, array-likes, and iterables

INPUTS

Browser APIs hand you many collection shapes: arrays, NodeList, arguments, strings, Sets, Maps, and custom iterables. Decide what you accept, then normalize at the boundary with Array.from or spread syntax. “Be liberal in what you accept” helps only when the trade-off is documented.

Accept iterable or array-like inputs deliberatelyPop out in the code editor (opens in a new tab)JavaScript
function toArray(input, { label = "items", acceptStrings = false } = {}) {  if (input == null) return [];  if (typeof input === "string" && !acceptStrings) {    throw new TypeError(label + " must be an iterable of values, not a string");  }  if (typeof input[Symbol.iterator] === "function") return Array.from(input);  if (Number.isInteger(input.length) && input.length >= 0) return Array.from(input);  throw new TypeError(label + " must be iterable or array-like");} const arrayLike = { 0: "Ada", 1: "Lin", length: 2 };console.log(toArray(new Set(["Ada", "Lin"])).join(", "));console.log(toArray(arrayLike).join(", "));console.log(toArray("JS", { acceptStrings: true }).join("|"));

Line 3 rejects strings unless the caller opts in, even though strings are iterable, because many APIs want a collection of items, not individual characters. Line 6 accepts real iterables such as Sets. Line 7 accepts array-like objects with numeric indexes and length. If your API accepts only arrays, say so and throw early for anything else.

What inputs does this API accept?
Iterable and array-like normalizerPop out in the code editor (opens in a new tab)JavaScript
function toArray(input, { label = "items", acceptStrings = false } = {}) {  if (input == null) return [];  if (typeof input === "string" && !acceptStrings) {    throw new TypeError(label + " must be an iterable of values, not a string");  }  if (typeof input[Symbol.iterator] === "function") return Array.from(input);  if (Number.isInteger(input.length) && input.length >= 0) return Array.from(input);  throw new TypeError(label + " must be iterable or array-like");} const arrayLike = { 0: "Ada", 1: "Lin", length: 2 };console.log(toArray(new Set(["Ada", "Lin"])).join(", "));console.log(toArray(arrayLike).join(", "));console.log(toArray("JS", { acceptStrings: true }).join("|"));
Selected inputset

Ada, Lin

Try it yourself

A Set is iterable, so Array.from reads its values in insertion order.

Being liberal in what you accept helps callers only when the accepted shapes are documented and normalized deliberately.

Deprecate APIs gracefully

STEP THROUGH

API design does not end when version one ships. When a name or signature is wrong, keep the old surface as an alias for a while, warn once with a migration path, update docs, publish a changelog, offer a codemod when possible, and remove the old API in a semver-major release.

Alias an old API and warn oncePop out in the code editor (opens in a new tab)JavaScript
function createToast(message, options = {}) {  const {    tone = "info",    duration = 3000,    dismissible = true,  } = options;  return { message, tone, duration, dismissible };} let didWarnAboutShowToast = false;function warnShowToastDeprecated() {  if (!didWarnAboutShowToast) {    console.warn("showToast(message, isError) is deprecated; use createToast(message, { tone }).");    didWarnAboutShowToast = true;  }} function showToast(message, isError = false) {  warnShowToastDeprecated();  return createToast(message, { tone: isError ? "danger" : "info" });} console.log(showToast("Saved", false).tone);console.log(showToast("Failed", true).tone);console.log(showToast("Again", true).tone);console.log(createToast("Queued", { tone: "success", duration: 1000 }).duration);

Lines 1 through 8 are the new API. Lines 10 through 15 store and update the one-time warning flag. Lines 18 through 20 keep the old boolean signature alive by translating it into the new options object. In documentation and TypeScript-aware editors, mark the old alias with @deprecated JSDoc and link to the replacement.

Deprecation shim replay
Step 0 of 12Ready
Your turn: follow the blue line

Graceful deprecation keeps old calls working, warns once with a clear replacement, and lets the new API stay clean.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
  const {    tone = "info",    duration = 3000,    dismissible = true,  } = options;  return { message, tone, duration, dismissible };} let didWarnAboutShowToast = false;function warnShowToastDeprecated() {  if (!didWarnAboutShowToast) {    console.warn("showToast(message, isError) is deprecated; use createToast(message, { tone }).");    didWarnAboutShowToast = true;  }} function showToast(message, isError = false) {  warnShowToastDeprecated();  return createToast(message, { tone: isError ? "danger" : "info" });} console.log(showToast("Saved", false).tone);console.log(showToast("Failed", true).tone);console.log(showToast("Again", true).tone);console.log(createToast("Queued", { tone: "success", duration: 1000 }).duration);
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
Node packages can use process.emitWarningJavaScript
process.emitWarning("showToast() is deprecated; use createToast().", {  type: "DeprecationWarning",  code: "DEP_SHOW_TOAST",});

Browser examples use console.warn. Node packages can use process.emitWarning with a DeprecationWarning type and stable code. Pair either warning with changelog notes, a version timeline, and a semver-major removal plan from npm package management.

Production API choices

SORT IT

Real projects combine all of these choices. A module might expose a small factory, a class, a utility function, and a deprecated alias. The goal is not one perfect shape; the goal is a consistent public surface that feels like one team designed it.

Where API design choices show up in production
Use caseLikely API shapeWhy it helps
Browser-style APIsMirror platform shapes such as addEventListener(type, listener, options) and fetch(url, init).Developers transfer expectations from built-ins.
Formatting utilitiesUse a required value plus an options object with defaults.The call site names optional behavior and can grow later.
DOM helpersAccept Iterable or array-like inputs only if documented.NodeList, arguments, strings, Sets, and arrays do not all mean the same thing.
Client librariesChoose consistent method names: get, list, create, update, delete.Predictable verbs make modules easier to scan.
PackagesUse @deprecated JSDoc, changelog notes, codemods, and semver majors.Deprecation is a migration plan, not just a warning.
Good API choice or trap?
  • `fetch(url, { method: "POST", headers })` mirrors platform conventions.
  • `render(root, true, false)` asks callers to remember two booleans.
  • An old alias logs one deprecation warning and delegates to the new function.
  • A DOM helper documents Iterable | ArrayLike and normalizes with Array.from.
  • A getUser function returns a cached object or a promise depending on cache state.
  • A package ships a codemod that rewrites showToast(message, true) to named options.
  • `setLimit(0)` throws `RangeError: limit must be at least 1`.
  • `fmtPrc(19.99, "USD")` abbreviates a public function name.
Try it yourself
0 of 8 correct

Sort each card by what it represents. Look for named choices, boolean traps, consistent timing, and migration aids.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Common misconceptions

  • “An options object is always better.” Required data should stay obvious. Use options for optional, named, extensible behavior.
  • “Accept every input shape.” Liberal input handling can hide bugs. Document what you accept and normalize deliberately.
  • “A fluent API is always cleaner.” Chaining is helpful for construction, but it can hide intermediate values and make debugging harder.
  • “Deprecation means delete it.” Deprecation means “this will go away later.” Keep compatibility while users migrate.
  • “Throwing is less friendly than returning errors.” Either can be friendly if it is consistent and documented. Mixing strategies is the real problem.
Three API shapes that are easy to confuse
ShapeExampleBest fitWatch out
Long positional parametersformatPrice(19.99, "USD", "en-US", true, 0.08)Small internal helpers where every argument is required and obvious.Hard to extend; booleans and numbers lose meaning at the call site.
Options objectformatPrice(19.99, { includeTax: true, taxRate: 0.08 })Public functions with optional choices, defaults, and future growth.Validate unknown options so typos do not silently do nothing.
Builder or fluent APIrequest("/search").query("q", value).build()Step-by-step construction, repeated calls, or validation in a final build().Chaining can hide intermediate values and make debugging harder.

Practice exercises

5 EXERCISES
Exercise 1 · Warm-upDefault an options object

Predict the output from a function that uses destructured defaults.

Starter codePop out in the code editor (opens in a new tab)JavaScript
function createToast(message, { tone = "info", duration = 3000 } = {}) {
  return tone + ":" + message + ":" + duration;
}
console.log(createToast("Saved", { duration: 1000 }));

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeRedesign a boolean trap

    Use the named wrapper and type the output that proves the old true, false call is no longer needed.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    function setVisible(element, visible, animate) {
      const state = visible ? "shown" : "hidden";
      const motion = animate ? "animated" : "instant";
      return element + ":" + state + ":" + motion;
    }
    function show(element, { animate = true } = {}) {
      return setVisible(element, true, animate);
    }
    console.log(show("toast", { animate: false }));

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeAccept any iterable

      Normalize an iterable and predict the printed names.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      function listNames(input) {
        return Array.from(input).join(" & ");
      }
      console.log(listNames(new Set(["Ada", "Lin"])));

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeAccept an array-like input

        Trace the array-like branch and type the output.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        function toArray(input) {
          if (typeof input[Symbol.iterator] === "function") return Array.from(input);
          if (Number.isInteger(input.length) && input.length >= 0) return Array.from(input);
          throw new TypeError("items must be iterable or array-like");
        }
        const fields = { 0: "button", 1: "input", length: 2 };
        console.log(toArray(fields).join("|"));

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeChallenge: add a deprecation alias

          Follow the alias and warning flag, then type the three printed values.

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          const warnings = [];
          function createToast(message, { tone = "info" } = {}) {
            return tone + ":" + message;
          }
          let warned = false;
          function showToast(message, isError = false) {
            if (!warned) {
              warnings.push("deprecated");
              warned = true;
            }
            return createToast(message, { tone: isError ? "danger" : "info" });
          }
          console.log(showToast("Saved", false));
          console.log(showToast("Failed", true));
          console.log(warnings.length);

          Answer, then press Check. Spacing and letter case don’t matter.

            Check your understanding

            8 QUESTIONS
            Lesson quiz · 8 questionsScore: first tries count
            1. Question 1 of 8What does API mean in this lesson?

              Choose an answer to see the explanation.

            2. Question 2 of 8What does this options object example print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              function normalize({ currency = "USD", includeTax = false } = {}) {
                return { currency, includeTax };
              }
              const settings = normalize({ includeTax: true });
              console.log(settings.currency + ":" + settings.includeTax);

              Choose an answer to see the explanation.

            3. Question 3 of 8Which redesign avoids the boolean trap best?

              Choose an answer to see the explanation.

            4. Question 4 of 8How many warnings should this deprecation shim record?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              let warned = false;
              let warnings = 0;
              function oldName() {
                if (!warned) {
                  warnings += 1;
                  warned = true;
                }
                return "ok";
              }
              oldName();
              oldName();
              console.log(warnings);

              Choose an answer to see the explanation.

            5. Question 5 of 8What does this array-like normalizer print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              function toArray(input) {
                if (typeof input[Symbol.iterator] === "function") return Array.from(input);
                return Array.from(input);
              }
              console.log(toArray({ 0: "a", 1: "b", length: 2 }).join(""));

              Choose an answer to see the explanation.

            6. Question 6 of 8Why is a function that sometimes returns a value and sometimes a promise a bad public API?

              Choose an answer to see the explanation.

            7. Question 7 of 8What error name prints here?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              function setLimit(limit) {
                if (!Number.isInteger(limit)) throw new TypeError("limit must be an integer");
                if (limit < 1) throw new RangeError("limit must be at least 1");
                return limit;
              }
              try {
                console.log(setLimit(0));
              } catch (error) {
                console.log(error.name);
              }

              Choose an answer to see the explanation.

            8. Question 8 of 8When does method chaining hurt more than it helps?

              Choose an answer to see the explanation.

            Key takeaways

            • Design the public surface of functions, classes, and modules, not just HTTP endpoints.
            • Use options objects for optional, extensible choices; keep required values visible.
            • Mirror platform conventions and keep naming, argument order, return shapes, async behavior, and errors consistent.
            • Use fluent APIs when chained steps help; avoid boolean traps and hard-to-debug chains.
            • Document accepted collection shapes and deprecate old APIs with warnings, aliases, codemods, changelogs, and semver majors.

            Remember the one-liner.
            A good API makes the correct call obvious and the surprising call difficult.

            This finishes the design patterns and architecture module. Up next, the curriculum moves into Build it from scratch, where you will rebuild platform behavior such as array methods to understand the contracts behind familiar APIs.

            CompleteFrontend Clear concepts. Working examples.