Author Watermark

Chapter 8: Default Parameters & Named Arguments

Prerequisites & Mental Check:

  • Chapter 1 & 2 Fundamentals: Function execution phases, arrow function syntax, and expression evaluation in memory.

  • Chapter 4 Concepts: Object destructuring and parameter destructuring in function signatures.

  • Chapter 6 Concepts: The 8 falsy values and why logical OR (||) causes silent bugs when handling 0, "", or false.

πŸ“Ί Video Explanation

Watch the full course with certification on : Udemy


As React Native applications scale, helper utilities, custom hooks, and UI components accept increasingly complex sets of arguments.

In vanilla JavaScript, passing multiple positional arguments is rigid and error-prone: you must remember the exact order, handle omitted parameters defensively, and avoid overriding valid falsy inputs. Combining ES6 Default Parameters with Object Destructuring solves this by enabling clean, self-documenting "named arguments" with built-in fallbacks.


1. The Problem: The Fragile Logical OR (||) Fallback

Before ES6, handling optional parameters required manual fallback assignments inside the function body:

JS
function greet(name) {
  name = name || "Athlete";
  console.log(`Hello, ${name}!`);
}

As established in [Chapter 6: Short-Circuit Evaluation & Optional Chaining], the logical OR operator (||) triggers on any falsy value (0, "", false, null, undefined, NaN). This creates silent bugs when 0 or empty strings represent valid input:

JS
function setVolume(level) {
  level = level || 50; // ❌ Overrides 0 with 50!
  console.log(`Volume: ${level}`);
}

setVolume(0); // Outputs: "Volume: 50" (Unexpected!)

2. ES6 Default Parameters: The Exact Evaluation Rule

Default parameters allow you to declare fallback values directly within the function parameter list:

JS
function greet(name = "Athlete") {
  console.log(`Hello, ${name}!`);
}

greet();          // "Hello, Athlete!" (No argument passed -> undefined)
greet("Subrata"); // "Hello, Subrata!"
greet(undefined); // "Hello, Athlete!" (undefined triggers the default)
greet(null);      // "Hello, null!"    (null does NOT trigger the default)

The Golden Trigger Rule

$$\text{Default Parameter Triggered} \iff \text{Passed Value} === \text{undefined}$$

Default parameters trigger only when the argument is undefined (either omitted or passed explicitly). They preserve valid falsy inputs like 0, "", and false:

JS
function setVolume(level = 50) {
  console.log(`Volume: ${level}`);
}

setVolume(0); // Outputs: "Volume: 0" βœ… Preserved correctly

3. Advanced Mechanics: Dynamic & Dependent Defaults

1. Defaults Can Reference Earlier Parameters

JavaScript evaluates parameters left to right in the current scope. A later default parameter can dynamically compute its value from an earlier parameter:

JS
function createUser(name, displayName = name) {
  return { name, displayName };
}

console.log(createUser("Subrata"));                  
// { name: "Subrata", displayName: "Subrata" }

console.log(createUser("Subrata", "Subrata Kumar")); 
// { name: "Subrata", displayName: "Subrata Kumar" }

2. Runtime Evaluation (Call-Time Execution)

Default parameter expressions are evaluated at runtime when the function is invoked, not when the script is parsed:

JS
let counter = 0;

function logCall(id = counter++) {
  console.log(`Call ID: ${id}`);
}

logCall(); // Call ID: 0
logCall(); // Call ID: 1
logCall(); // Call ID: 2

Because defaults evaluate at call time, functions like date = new Date().toISOString() produce the exact timestamp of the call rather than the time the module was imported.


4. "Named Arguments" via Destructuring

Languages like Python and Kotlin support native named arguments (createUser(name="Alex", role="Admin")). JavaScript does not have native named parametersβ€”calling createUser("Alex", "Admin", true) requires you to memorize the exact positional order.

The JavaScript Solution: Destructured Configuration Objects

By accepting a single configuration object and destructuring it in the parameter list (using the principles from [Chapter 4: Object & Array Destructuring]), you simulate named arguments with default fallbacks:

JS
// ❌ Positional: Order-dependent, unclear at call-site
function createUser(name, role, isActive) { / ... / }
createUser("Subrata", "admin", true);

// βœ… Named Destructuring: Order-independent, self-documenting
function createUser({ name, role = "member", isActive = true }) {
  return { name, role, isActive };
}

createUser({ name: "Subrata", role: "admin" });
// { name: "Subrata", role: "admin", isActive: true }
CODE
Positional Parameters(Rigid)
createUser("Subrata",    "admin",     true)
       β”‚                    β”‚          β”‚
       β–Ό                    β–Ό          β–Ό
   (Param 1: name,     Param 2: role, Param 3: isActive)

Named Object Destructuring(Flexible)
createUser({ role: "admin", name: "Subrata" })
                 β”‚                 β”‚
                 β–Ό                 β–Ό
   (Extracted by Key: role)   (Extracted by Key: name)

5. The = {} Safety Net Pattern

When destructuring an object parameter, a missing argument will cause a runtime crash if you omit the fallback wrapper = {}:

JS
// ❌ CRASHES if called without arguments
const useDebounce = (value, { delay = 300 }) => { / ... / };

useDebounce("search"); 
// πŸ’₯ TypeError: Cannot destructure property 'delay' of 'undefined'

Why does this happen?

When the second argument is omitted, JavaScript passes undefined. The engine attempts to unpack { delay } from undefined, causing a runtime TypeError.

The Two-Level Fallback Solution:

JS
// βœ… Safe: Defaults the entire parameter to {}, then unpacks delay
const useDebounce = (value, { delay = 300, immediate = false } = {}) => {
  console.log(`Delay: ${delay}, Immediate: ${immediate}`);
};

useDebounce("search", { delay: 500 }); // Delay: 500, Immediate: false
useDebounce("search");                 // Delay: 300, Immediate: false βœ… Works safely!

$$\begin{aligned} \textbf{Outer Fallback (} = {} \textbf{)} &\longrightarrow \text{Protects against missing parameter argument (\texttt{undefined})} \ \textbf{Inner Fallback (} \text{key} = \text{val} \textbf{)} &\longrightarrow \text{Protects against missing property on passed object} \end{aligned}$$


6. Real-World Use Cases in React Native

1. Reusable UI Components with Computed Style Defaults

REACT
import React from 'react';
import { Image, StyleSheet } from 'react-native';

const Avatar = ({ 
  uri, 
  size = 48, 
  borderRadius = size / 2, // Dependent default parameter!
  borderColor = "#E2E8F0" 
}) => (
  <Image
    source={{ uri }}
    style={{ 
      width: size, 
      height: size, 
      borderRadius, 
      borderWidth: 2, 
      borderColor 
    }}
  />
);

// Call sites are clear and self-documenting:
<Avatar uri="https://example.com/user.png" size={64} />

2. Flexible API Service Helpers

JS
const BASE_URL = "https://api.healthapp.com";

const fetchSteps = async ({ 
  userId, 
  startDate, 
  endDate = new Date().toISOString() // Dynamic call-time default
}) => {
  const response = await fetch(
    `${BASE_URL}/users/${userId}/steps?from=${startDate}&to=${endDate}`
  );
  return response.json();
};

// Caller omits endDate safely:
fetchSteps({ userId: "u_123", startDate: "2026-06-01" });

3. Custom Hook Configuration Wrappers

JS
const useStepTracker = ({ 
  targetGoal = 10000, 
  autoSync = true, 
  intervalMs = 5000 
} = {}) => {
  // Hook logic implementation
};

// Safe invocation with zero config:
useStepTracker();

7. Architectural Comparison: Argument Passing Strategies

Strategy Syntax Call-Site Clarity Order Dependent? Safety on Missing Args
Positional Parameters fn(a, b, c) Low (unclear with 3+ args) Strict Order Requires trailing defaults
Manual OR Check `a = a def` Medium
ES6 Default Parameters fn(a = 10) Medium Strict Order βœ… Triggers only on undefined
Destructured Object (= {}) fn({ a = 10 } = {}) High (Named Args) Independent βœ… Bulletproof at all levels

The Decision Rule

JS
Number of function arguments?
  β”œβ”€β”€ 1 to 2 parameters ──► Standard positional parameters with default values
  └── 3 or more parameters ──► Destructured configuration object with `{ ... } = {}`

8. Common Pitfalls & Mistakes

Pitfall 1: Using || Fallbacks for Numerical Properties

JS
// ❌ WRONG: Passing 0 forces opacity to 1.0
function renderOverlay(opacity) {
  const finalOpacity = opacity || 1.0;
}

// βœ… CORRECT: Preserves 0 as a valid transparency level
function renderOverlay(opacity = 1.0) {
  // opacity === 0 is respected
}

Pitfall 2: Omitting the Outer = {} on Destructured Configs

JS
// ❌ CRASHES if user passes no arguments
const initLogger = ({ prefix = "[APP]", level = "info" }) => {};
initLogger(); // TypeError: Cannot destructure property of undefined

// βœ… SAFE: Works with or without arguments passed
const initLogger = ({ prefix = "[APP]", level = "info" } = {}) => {};
initLogger(); // prefix: "[APP]", level: "info"

Pitfall 3: Expecting null to Trigger Default Values

JS
function loadTheme(theme = "dark") {
  console.log(theme);
}

loadTheme(null); // Logs: null (NOT "dark"!)

Remember: null represents an explicit value; defaults trigger only on undefined.


Technical Interview Questions

Q1: What will be logged to the console by the following code?

JS
function compute(a = 10, b = 20, c = a + b) {
  console.log(a, b, c);
}

compute(undefined, 5);
compute(null, 5);
TEXT
10 5 15
null 5 5

Q2: Will the following function throw an error when executed? Why or why not?

JS
function configure(width = height, height = 100) {
  return { width, height };
}
configure(undefined, 200);

Q3: Convert the following multi-argument function into a safe "named arguments" signature with default values:

JS
function createNotification(title, message, isSticky, timeoutMs, priority) {
  // implementation
}
JS
function createNotification({
  title,
  message = "",
  isSticky = false,
  timeoutMs = 4000,
  priority = "normal"
} = {}) {
  return { title, message, isSticky, timeoutMs, priority };
}

Q4: How do default parameters affect the .length property of a function in JavaScript?

JS
function fn1(a, b, c) {}            // fn1.length === 3
function fn2(a, b = 10, c) {}       // fn2.length === 1
function fn3({ a, b } = {}) {}      // fn3.length === 0

Q5: Why does ({ data = "Empty" } = {}) require two separate assignments of = , and what role does each play?

  1. The inner = "Empty" provides a fallback for the property data when an object is passed but does not contain a data key (e.g., fn({}) $\rightarrow$ data = "Empty").

  2. The outer = {} provides a fallback for the entire argument when no argument is supplied at all (e.g., fn() $\rightarrow$ argument defaults to {}, which then defaults data to "Empty"). Omitting the outer fallback causes a TypeError when reading properties from undefined.