Practise JavaScript functions with 32 programs, each with a full solution, the exact output and a short explanation. The programs start with the basics, move to intermediate problems and finish with real-world scenarios from test automation, such as test data, prices, form inputs and test results. They are written for testers learning JavaScript for Playwright.
How to practise: read the task, write your own solution first, then open Show solution to compare. Run any program with node program.js.
New to this topic? Read the JavaScript Functions lesson first.
- Beginner programs (1–10)
- Intermediate programs (11–20)
- Real-world QA scenario programs (21–32)
JavaScript Beginner Programs
1. Function that greets a userBeginner
Write a function greet(name) that returns Hello, <name>! and print it for two names.
Show solution
function greet(name) {
return `Hello, ${name}!`;
}
console.log(greet('Asha'));
console.log(greet('Ravi'));
Output
Hello, Asha!
Hello, Ravi!
Explanation: a function declaration takes parameters and uses return to send a value back to the caller.
2. Add two numbersBeginner
Write add(a, b) and print the sum of 12 and 30.
Show solution
function add(a, b) {
return a + b;
}
const result = add(12, 30);
console.log('Result:', result);
Output
Result: 42
Explanation: the returned value can be stored in a variable or used directly.
3. Arrow functionBeginner
Rewrite a square function as an arrow function and print the squares of 4 and 9.
Show solution
const square = n => n * n;
console.log(square(4));
console.log(square(9));
Output
16
81
Explanation: arrow functions with one expression return it automatically, without return or braces.
4. Default parametersBeginner
Write openPage(url, timeout = 30000) that prints the URL and timeout, and call it with and without a timeout.
Show solution
function openPage(url, timeout = 30000) {
console.log(`Opening ${url} (timeout ${timeout} ms)`);
}
openPage('/login');
openPage('/reports', 60000);
Output
Opening /login (timeout 30000 ms)
Opening /reports (timeout 60000 ms)
Explanation: default parameters are used when the argument is missing or undefined.
5. Function without returnBeginner
Show what a function without a return statement gives back.
Show solution
function logStep(step) {
console.log('Step:', step);
}
const value = logStep('Click login');
console.log('Returned:', value);
Output
Step: Click login
Returned: undefined
Explanation: a function with no return returns undefined.
6. Even checkerBeginner
Write isEven(n) that returns true or false, and test it with 4 and 7.
Show solution
const isEven = n => n % 2 === 0;
console.log(isEven(4));
console.log(isEven(7));
Output
true
false
Explanation: functions that return booleans read naturally in conditions, for example if (isEven(x)).
7. Celsius to FahrenheitBeginner
Convert 0, 37 and 100 °C to Fahrenheit with a function.
Show solution
function toFahrenheit(celsius) {
return (celsius * 9) / 5 + 32;
}
for (const c of [0, 37, 100]) console.log(`${c}°C = ${toFahrenheit(c)}°F`);
Output
0°C = 32°F
37°C = 98.6°F
100°C = 212°F
Explanation: wrapping a formula in a function makes it reusable and easy to test.
8. Function expressionBeginner
Store an anonymous function in a constant and call it.
Show solution
const multiply = function (a, b) {
return a * b;
};
console.log(multiply(6, 7));
console.log(typeof multiply);
Output
42
function
Explanation: functions are values in JavaScript, so they can be stored in variables.
9. Rest parametersBeginner
Write sum(...numbers) that adds any number of arguments.
Show solution
function sum(...numbers) {
let total = 0;
for (const n of numbers) total += n;
return total;
}
console.log(sum(1, 2));
console.log(sum(5, 10, 15, 20));
Output
3
50
Explanation: ...numbers collects all arguments into a real array.
10. Return multiple valuesBeginner
Return the min and max of a list as an object and destructure the result.
Show solution
function minMax(values) {
return { min: Math.min(...values), max: Math.max(...values) };
}
const { min, max } = minMax([42, 7, 19, 88]);
console.log('min:', min, 'max:', max);
Output
min: 7 max: 88
Explanation: return an object or array when a function produces more than one value.
JavaScript Intermediate Programs
11. Hoisting of function declarationsIntermediate
Call a function declaration before it is defined, and compare with a function expression.
Show solution
console.log(double(5));
function double(n) { return n * 2; }
try {
triple(5);
} catch (error) {
console.log(error.name);
}
const triple = n => n * 3;
Output
10
ReferenceError
Explanation: function declarations are hoisted with their body. A const function is in the temporal dead zone until its line runs.
12. Callback functionIntermediate
Write runTest(name, callback) that prints the name and calls the callback with a result.
Show solution
function runTest(name, callback) {
console.log('Running', name);
callback('passed');
}
runTest('login', result => console.log('Result:', result));
Output
Running login
Result: passed
Explanation: a callback is a function passed to another function to be called later.
13. Higher-order function that returns a functionIntermediate
Write makeMultiplier(factor) that returns a function, and create double and triple.
Show solution
function makeMultiplier(factor) {
return n => n * factor;
}
const double = makeMultiplier(2);
const triple = makeMultiplier(3);
console.log(double(10), triple(10));
Output
20 30
Explanation: functions can return functions. The returned function remembers factor through a closure.
14. Closure counterIntermediate
Create a counter function that keeps its own private count.
Show solution
function createCounter() {
let count = 0;
return () => ++count;
}
const next = createCounter();
console.log(next(), next(), next());
const other = createCounter();
console.log(other());
Output
1 2 3
1
Explanation: each call to createCounter creates a new count that only its returned function can access.
15. Recursion: factorialIntermediate
Calculate 6! with a recursive function.
Show solution
function factorial(n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
console.log(factorial(6));
Output
720
Explanation: a recursive function calls itself with a smaller input until it reaches a base case.
16. Recursion: sum nested arraysIntermediate
Sum all numbers in [1, [2, 3], [4, [5]]] with recursion.
Show solution
function deepSum(items) {
let total = 0;
for (const item of items) {
total += Array.isArray(item) ? deepSum(item) : item;
}
return total;
}
console.log(deepSum([1, [2, 3], [4, [5]]]));
Output
15
Explanation: recursion handles data whose nesting depth is not known in advance.
17. Pure vs impure functionsIntermediate
Compare a pure function with one that changes outside state.
Show solution
let calls = 0;
const impureAdd = (a, b) => { calls++; return a + b; };
const pureAdd = (a, b) => a + b;
console.log(pureAdd(2, 3), pureAdd(2, 3));
console.log(impureAdd(2, 3), impureAdd(2, 3), 'calls:', calls);
Output
5 5
5 5 calls: 2
Explanation: a pure function always returns the same output for the same input and changes nothing outside itself, which makes it easy to test.
18. Immediately invoked functionIntermediate
Use an IIFE to compute a value without leaking variables.
Show solution
const config = (() => {
const env = 'qa';
const port = 8080;
return `${env}:${port}`;
})();
console.log(config);
console.log(typeof env);
Output
qa:8080
undefined
Explanation: an IIFE runs immediately and keeps its variables private.
19. Memoize a slow functionIntermediate
Cache results of a function so repeated calls are not recalculated.
Show solution
function memoize(fn) {
const cache = new Map();
return n => {
if (cache.has(n)) return `${cache.get(n)} (cached)`;
const value = fn(n);
cache.set(n, value);
return value;
};
}
const slowSquare = n => n * n;
const fastSquare = memoize(slowSquare);
console.log(fastSquare(12));
console.log(fastSquare(12));
Output
144
144 (cached)
Explanation: a closure keeps the cache between calls. Memoization trades memory for speed.
20. Arguments with destructured optionsIntermediate
Write createUser({ name, role = 'viewer', active = true }) and call it with partial options.
Show solution
function createUser({ name, role = 'viewer', active = true }) {
return `${name} | ${role} | ${active ? 'active' : 'inactive'}`;
}
console.log(createUser({ name: 'Asha' }));
console.log(createUser({ name: 'Ravi', role: 'admin', active: false }));
Output
Asha | viewer | active
Ravi | admin | inactive
Explanation: an options object with destructuring and defaults keeps calls readable when a function has many settings.
JavaScript Real-World QA Scenario Programs
21. Reusable assertion helperReal-world
Write assertEqual(actual, expected, message) that prints PASS or FAIL.
Show solution
function assertEqual(actual, expected, message) {
const ok = actual === expected;
console.log(`${ok ? 'PASS' : 'FAIL'}: ${message}${ok ? '' : ` (expected ${expected}, got ${actual})`}`);
return ok;
}
assertEqual(2 + 2, 4, 'adds numbers');
assertEqual('Login'.toLowerCase(), 'login', 'lower-cases text');
assertEqual(10 / 4, 2, 'divides numbers');
Output
PASS: adds numbers
PASS: lower-cases text
FAIL: divides numbers (expected 2, got 2.5)
Explanation: small helpers make checks consistent. Real tests use Playwright’s expect, which works the same way.
22. Build a URL helperReal-world
Write buildUrl(base, path, params) that joins a base URL, a path and query parameters.
Show solution
function buildUrl(base, path, params = {}) {
const url = new URL(path, base);
for (const [key, value] of Object.entries(params)) url.searchParams.set(key, value);
return url.toString();
}
console.log(buildUrl('https://shop.test', '/search', { q: 'laptop', page: 2 }));
console.log(buildUrl('https://shop.test', '/cart'));
Output
https://shop.test/search?q=laptop&page=2
https://shop.test/cart
Explanation: a helper avoids hand-building URLs, and URL takes care of encoding query values.
23. Generate unique test emailsReal-world
Write uniqueEmail(prefix, id) that returns a unique email address.
Show solution
const uniqueEmail = (prefix, id) => `${prefix}.${id}@example.com`;
for (let i = 1; i <= 3; i++) console.log(uniqueEmail('qa.signup', 1000 + i));
Output
qa.signup.1001@example.com
qa.signup.1002@example.com
qa.signup.1003@example.com
Explanation: a tiny pure function keeps test-data rules in one place.
24. Retry wrapperReal-world
Write retry(fn, attempts) that calls a function until it succeeds or attempts run out.
Show solution
function retry(fn, attempts) {
for (let i = 1; i <= attempts; i++) {
try {
return fn(i);
} catch (error) {
console.log(`Attempt ${i} failed: ${error.message}`);
}
}
throw new Error('All attempts failed');
}
const flaky = attempt => {
if (attempt < 3) throw new Error('element not ready');
return 'clicked';
};
console.log(retry(flaky, 5));
Output
Attempt 1 failed: element not ready
Attempt 2 failed: element not ready
clicked
Explanation: wrapping retry logic in a function means every flaky step can reuse the same behaviour.
25. Format test durationReal-world
Write formatDuration(ms) that returns values like 850 ms, 2.5 s or 1m 5s.
Show solution
function formatDuration(ms) {
if (ms < 1000) return `${ms} ms`;
if (ms < 60000) return `${(ms / 1000).toFixed(1)} s`;
const minutes = Math.floor(ms / 60000);
const seconds = Math.round((ms % 60000) / 1000);
return `${minutes}m ${seconds}s`;
}
for (const ms of [850, 2500, 65000]) console.log(formatDuration(ms));
Output
850 ms
2.5 s
1m 5s
Explanation: one formatting function keeps reports consistent.
26. Validate form inputReal-world
Write validateSignup(data) that returns a list of error messages.
Show solution
function validateSignup({ email = '', password = '', age }) {
const errors = [];
if (!email.includes('@')) errors.push('Email is invalid');
if (password.length < 8) errors.push('Password is too short');
if (!(age >= 18)) errors.push('Must be 18 or older');
return errors;
}
const show = errors => console.log(errors.length ? errors.join('; ') : 'No errors');
show(validateSignup({ email: 'qa@test.com', password: 'Secret123', age: 25 }));
show(validateSignup({ email: 'qa', password: '123', age: 16 }));
Output
No errors
Email is invalid; Password is too short; Must be 18 or older
Explanation: returning all errors at once lets a test assert every validation message on the form.
27. Debounce a searchReal-world
Write a debounce function so that only the last of several quick calls runs.
Show solution
function debounce(fn, delay) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}
const search = debounce(term => console.log('Searching for:', term), 30);
search('p');
search('pl');
search('play');
await new Promise(resolve => setTimeout(resolve, 60));
Output
Searching for: play
Explanation: debouncing waits until calls stop, which is how search boxes avoid one request per keystroke.
28. Compose small functionsReal-world
Combine trim, lowercase and slugify steps into one pipeline function.
Show solution
const pipe = (...fns) => value => fns.reduce((acc, fn) => fn(acc), value);
const trim = s => s.trim();
const lower = s => s.toLowerCase();
const slugify = s => s.replace(/\s+/g, '-');
const toSlug = pipe(trim, lower, slugify);
console.log(toSlug(' Playwright Mock Test '));
Output
playwright-mock-test
Explanation: composing small single-purpose functions builds bigger behaviour that is easy to test piece by piece.
29. Price calculator with optionsReal-world
Write calculateTotal(items, { taxRate, coupon }) and test two carts.
Show solution
function calculateTotal(items, { taxRate = 0.18, coupon = 0 } = {}) {
const subtotal = items.reduce((sum, item) => sum + item.price * item.qty, 0);
const discounted = Math.max(subtotal - coupon, 0);
return Math.round(discounted * (1 + taxRate));
}
const cart = [{ price: 500, qty: 2 }, { price: 250, qty: 1 }];
console.log(calculateTotal(cart));
console.log(calculateTotal(cart, { coupon: 250, taxRate: 0.05 }));
Output
1475
1050
Explanation: an options object with defaults keeps the function easy to call for common cases.
30. Callback for each test resultReal-world
Write runSuite(tests, onResult) that runs simple checks and calls onResult for each.
Show solution
function runSuite(tests, onResult) {
for (const test of tests) {
const passed = test.check();
onResult(test.name, passed);
}
}
const tests = [
{ name: 'title is set', check: () => 'Shop'.length > 0 },
{ name: 'cart is empty', check: () => [1].length === 0 }
];
runSuite(tests, (name, passed) => console.log(`${passed ? '✔' : '✘'} ${name}`));
Output
✔ title is set
✘ cart is empty
Explanation: passing a callback lets the caller decide how results are reported, such as in the console, a file or a dashboard.
31. Run a function only onceReal-world
Write a once function that lets a warning print only the first time.
Show solution
function once(fn) {
let called = false;
return (...args) => {
if (called) return;
called = true;
fn(...args);
};
}
const warnDeprecated = once(() => console.log('Warning: page.$() is discouraged, use locators'));
warnDeprecated();
warnDeprecated();
warnDeprecated();
Output
Warning: page.$() is discouraged, use locators
Explanation: a closure flag prevents the same message from flooding the logs.
32. Page-object style functionReal-world
Write a factory createLoginPage() that returns an object with methods that record actions.
Show solution
function createLoginPage() {
const actions = [];
return {
fill(field, value) { actions.push(`fill ${field}=${field === 'password' ? '***' : value}`); return this; },
submit() { actions.push('click submit'); return this; },
history() { return actions.join(' -> '); }
};
}
const page = createLoginPage().fill('email', 'qa@test.com').fill('password', 'secret').submit();
console.log(page.history());
Output
fill email=qa@test.com -> fill password=*** -> click submit
Explanation: returning this from each method allows chaining. This is the same idea as a page object with readable steps.