Parameters, Arguments, and Return Values

7 min read

The previous lesson introduced functions. This one zooms in on the bits that move data in and out of them — the parameters that name what comes in, the arguments that supply real values, and the return statement that hands a result back. Get these right and your test helpers stay small, focused, and easy to compose.

Parameters vs arguments

Two words for two related-but-different things:

  • Parameters are the named placeholders in the function definition.
  • Arguments are the actual values you pass at the call site.
function validateResponse(statusCode, body) {  // statusCode, body — parameters
  console.log(statusCode, body);
}
 
validateResponse(200, { userId: 42 });          // 200, { userId: 42 } — arguments

Most people use the words interchangeably in conversation, and that's fine. The distinction matters when you read errors or library documentation: "expected 2 arguments, got 1" means the call site is missing a value, not the definition.

Multiple parameters

Functions can take any number of parameters, separated by commas. Inside the body, they're regular variables.

function validateResponse(statusCode, body, responseTimeMs) {
  if (statusCode !== 200)    return "❌ bad status";
  if (!body)                 return "❌ empty body";
  if (responseTimeMs > 2000) return "❌ too slow";
  return "✅ pass";
}
 
console.log(validateResponse(200, { userId: 42 }, 1450));

Output:

✅ pass

Order matters — argument number 1 binds to parameter 1, argument number 2 to parameter 2, and so on. Pass them in the wrong order and you get plausible-but-wrong behaviour with no syntax error.

Default parameters

Some parameters have a sensible default. JavaScript lets you set one with = in the parameter list — the default kicks in only when the caller omits the argument or passes undefined.

function login(username, password = "Test@1234") {
  console.log(`Logging in as ${username} with ${password}`);
}
 
login("alice");                  // uses default password
login("bob", "MyOwnPassword!");  // uses provided password

Output:

Logging in as alice with Test@1234
Logging in as bob with MyOwnPassword!

Defaults are everywhere in test helpers — a default base URL, a default timeout, a default test password used by every fixture. They keep call sites short and let one helper serve every test.

The return keyword

return does two things at once: it sends a value back to the caller, and it ends the function immediately. Anything after a return in the same execution path doesn't run.

function calculatePassRate(passed, total) {
  if (total === 0) return 0;          // guard against divide-by-zero
  return (passed / total) * 100;
}
 
console.log(calculatePassRate(28, 32));  // 87.5
console.log(calculatePassRate(0, 0));    // 0

Output:

87.5
0

A function without a return (or one that hits the end of the body) returns undefined — the same undefined you met in lesson 4 of chapter 1.

function logIt(msg) {
  console.log(msg);
}
 
const result = logIt("hello");
console.log(result);  // undefined

That's why beginners sometimes get confused: "I called the function but result is undefined." The function never returned anything; it only had a side effect.

Guard clauses with early returns

A guard clause is an early return that handles an edge case at the top of the function, leaving the main logic uncluttered. It's the same early-return pattern from the if/else lesson — applied to function bodies.

function getUserName(user) {
  if (!user)        return "guest";
  if (!user.name)   return "anonymous";
  return user.name;
}
 
console.log(getUserName({ name: "Alice" }));   // Alice
console.log(getUserName({}));                  // anonymous
console.log(getUserName(null));                // guest

Output:

Alice
anonymous
guest

Each guard kicks the bad input out the front door; the happy path lives at the bottom of the function, never indented past the first level. That style scales — you can add new guards without re-shuffling existing logic.

Storing the return value

Functions that return values are most useful when you capture the result. Store it in a variable; pass it to another function; print it; whatever the test needs.

function buildApiUrl(base, endpoint, query = "") {
  const tail = query ? `?${query}` : "";
  return `${base}${endpoint}${tail}`;
}
 
const url = buildApiUrl("https://api.staging.com", "/users", "role=admin");
console.log(url);

Output:

https://api.staging.com/users?role=admin

The third parameter has a default of "", so callers who don't need a query string can skip it: buildApiUrl(base, "/health") returns https://api.staging.com/health.

Data flow through a function

Step 1 of 5

Caller passes arguments

validateResponse(200, body, 1450) — three real values are supplied at the call site.

Every function call follows this five-step dance. Once you can see the dance, debugging "why did my function return undefined?" gets a lot faster.

A complete example

Putting parameters, defaults, guards, and return values together:

function summariseRun(passed, failed, skipped = 0) {
  const total = passed + failed + skipped;
  if (total === 0) return "No tests were run";
  const passRate = ((passed / total) * 100).toFixed(1);
  return `${passed}/${total} passed (${passRate}%) — ${failed} failed, ${skipped} skipped`;
}
 
console.log(summariseRun(28, 4));        // skipped defaults to 0
console.log(summariseRun(28, 4, 2));     // explicit skipped count
console.log(summariseRun(0, 0, 0));      // guard branch

Output:

28/32 passed (87.5%) — 4 failed, 0 skipped
28/34 passed (82.4%) — 4 failed, 2 skipped
No tests were run

That's a fully reusable test summary helper — every part you've learned so far in one function.

⚠️ Common mistakes

  • Forgetting return. A function that's supposed to return a value but doesn't will silently return undefined. The bug shows up at the call site ("why is result undefined?") and you'll spend twice as long looking there as you should at the function.
  • Argument order mistakes. calculatePassRate(total, passed) instead of calculatePassRate(passed, total) — both are numbers, no syntax error, wildly wrong percentage. Match the parameter order in the definition. For functions with many parameters, an options object (function f({ passed, failed, skipped })) is often clearer.
  • Defaults that aren't safe. function fetchUser(id = 1) defaults to user 1 — fine in test fixtures, dangerous in code that might run in production. Pick defaults that are safe everywhere the function might be called.

🎯 Practice task

Build a tiny test reporting library. 15-20 minutes.

  1. In your js-for-qa folder, create report.js.
  2. Write calculatePassRate(passed, total) with a guard for total === 0.
  3. Write buildApiUrl(base, endpoint, query = "") like the example above.
  4. Write summariseRun(passed, failed, skipped = 0) that returns the formatted string.
  5. Call each function with a few different inputs and console.log each result.
  6. Run with node report.js and confirm the outputs match what you expect.
  7. Stretch: add a wasFlaky(passed, total, threshold = 0.95) boolean function that returns true when the pass rate is between threshold and 1.0 exclusive — i.e., it mostly passed but isn't a stable green. Try a few inputs.

The next lesson introduces a shorter way to write functions — arrow functions — which you'll see everywhere in modern test code.

// tip to track lessons you complete and pick up where you left off across devices.