Practise JavaScript variables 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 Variables lesson first.
- Beginner programs (1–10)
- Intermediate programs (11–20)
- Real-world QA scenario programs (21–32)
JavaScript Beginner Programs
1. Declare and print variablesBeginner
Declare a variable testerName with your name and a variable experienceYears with a number, then print both.
Show solution
let testerName = 'Asha';
let experienceYears = 3;
console.log('Tester:', testerName);
console.log('Experience (years):', experienceYears);
Output
Tester: Asha
Experience (years): 3
Explanation: let declares a variable that holds a value. console.log prints its arguments separated by a space.
2. Reassign a let variableBeginner
Create browser with the value 'chromium', print it, change it to 'firefox' and print it again.
Show solution
let browser = 'chromium';
console.log('Before:', browser);
browser = 'firefox';
console.log('After:', browser);
Output
Before: chromium
After: firefox
Explanation: variables declared with let can be reassigned. The second assignment replaces the old value.
3. Constants cannot be reassignedBeginner
Store a base URL in a const, then try to reassign it inside try/catch and print the error message.
Show solution
const BASE_URL = 'https://qa.example.com';
try {
BASE_URL = 'https://prod.example.com';
} catch (error) {
console.log('Error:', error.message);
}
console.log('BASE_URL is still', BASE_URL);
Output
Error: Assignment to constant variable.
BASE_URL is still https://qa.example.com
Explanation: a const binding cannot be reassigned, so the assignment throws a TypeError. The original value stays unchanged.
4. Swap two variables with a temporary variableBeginner
Swap the values of a = 10 and b = 20 using a third variable and print them before and after.
Show solution
let a = 10;
let b = 20;
console.log('Before swap: a =', a, 'b =', b);
let temp = a;
a = b;
b = temp;
console.log('After swap: a =', a, 'b =', b);
Output
Before swap: a = 10 b = 20
After swap: a = 20 b = 10
Explanation: the temporary variable keeps the first value safe while it is overwritten.
5. Swap two variables with destructuringBeginner
Swap first = 'login' and second = 'logout' without a temporary variable.
Show solution
let first = 'login';
let second = 'logout';
[first, second] = [second, first];
console.log(first, second);
Output
logout login
Explanation: array destructuring builds a temporary array [second, first] and unpacks it back into the two variables in one step.
6. Declare several variables in one statementBeginner
Declare passed, failed and skipped test counts in a single let statement and print the total.
Show solution
let passed = 42, failed = 3, skipped = 5;
let total = passed + failed + skipped;
console.log('Total tests:', total);
Output
Total tests: 50
Explanation: one let can declare several comma-separated variables. Readability usually favours one per line in real projects.
7. Uninitialised variableBeginner
Declare result without a value and print it and its type.
Show solution
let result;
console.log(result);
console.log(typeof result);
Output
undefined
undefined
Explanation: a declared but unassigned variable holds undefined, whose type is also 'undefined'.
8. Build a message with a template literalBeginner
Use variables user = 'qa_admin' and role = 'Admin' to print Logged in as qa_admin (Admin).
Show solution
const user = 'qa_admin';
const role = 'Admin';
const message = `Logged in as ${user} (${role})`;
console.log(message);
Output
Logged in as qa_admin (Admin)
Explanation: template literals (backticks) insert variable values with ${...}.
9. Increment a counterBeginner
Start retryCount at 0, increase it three times using different syntax and print it after each step.
Show solution
let retryCount = 0;
retryCount = retryCount + 1;
console.log(retryCount);
retryCount += 1;
console.log(retryCount);
retryCount++;
console.log(retryCount);
Output
1
2
3
Explanation: x = x + 1, x += 1 and x++ all add one to a let variable.
10. Valid variable namesBeginner
Create variables named with camelCase, a leading underscore and a dollar sign, then print them together.
Show solution
const firstName = 'Ravi';
const _internalId = 101;
const $price = 499;
console.log(firstName, _internalId, $price);
Output
Ravi 101 499
Explanation: names may start with a letter, _ or $, and must not start with a digit. camelCase is the usual convention.
JavaScript Intermediate Programs
11. Block scope with letIntermediate
Declare status outside an if block and a different status inside it. Print both to show they are separate.
Show solution
let status = 'outer';
if (true) {
let status = 'inner';
console.log('Inside block:', status);
}
console.log('Outside block:', status);
Output
Inside block: inner
Outside block: outer
Explanation: let is block-scoped, so the inner status exists only inside the braces and does not affect the outer one.
12. var ignores block scopeIntermediate
Repeat the previous program using var and observe the difference.
Show solution
var status = 'outer';
if (true) {
var status = 'inner';
console.log('Inside block:', status);
}
console.log('Outside block:', status);
Output
Inside block: inner
Outside block: inner
Explanation: var is function-scoped, so both declarations refer to the same variable and the block overwrites it.
13. Hoisting with varIntermediate
Print a var variable before the line that declares it.
Show solution
console.log('Before declaration:', environment);
var environment = 'staging';
console.log('After declaration:', environment);
Output
Before declaration: undefined
After declaration: staging
Explanation: var declarations are hoisted and initialised with undefined, so the first log prints undefined instead of throwing.
14. Temporal dead zone with letIntermediate
Access a let variable before its declaration inside try/catch and print the error name.
Show solution
try {
console.log(timeout);
let timeout = 30000;
} catch (error) {
console.log(error.name + ': ' + error.message);
}
Output
ReferenceError: Cannot access 'timeout' before initialization
Explanation: let and const are hoisted but not initialised. Using them before the declaration throws a ReferenceError (the temporal dead zone).
15. const object can still changeIntermediate
Store test settings in a const object, change one property and print the object.
Show solution
const settings = { headless: true, retries: 0 };
settings.retries = 2;
console.log(settings);
Output
{ headless: true, retries: 2 }
Explanation: const stops reassignment of the variable, not changes to the object it points to.
16. Freeze a configuration objectIntermediate
Freeze a config object, try to change it and print it to show the change was ignored.
Show solution
const config = Object.freeze({ browser: 'chromium', workers: 4 });
config.workers = 8;
console.log(config.workers);
console.log(Object.isFrozen(config));
Output
4
true
Explanation: Object.freeze makes properties read-only. In non-strict code the assignment is silently ignored.
17. Loop variable scope: let vs varIntermediate
Collect functions in a loop using let and using var, then call them to see which values they remember.
Show solution
const withLet = [];
for (let i = 0; i < 3; i++) withLet.push(() => i);
const withVar = [];
for (var j = 0; j < 3; j++) withVar.push(() => j);
console.log('let:', withLet.map(f => f()));
console.log('var:', withVar.map(f => f()));
Output
let: [ 0, 1, 2 ]
var: [ 3, 3, 3 ]
Explanation: let creates a new binding for each loop iteration. var shares one variable, so every function sees its final value 3.
18. Shadowing in nested blocksIntermediate
Use the same name level in three nested scopes and print it from each one.
Show solution
const level = 'file';
{
const level = 'block';
{
const level = 'nested block';
console.log(level);
}
console.log(level);
}
console.log(level);
Output
nested block
block
file
Explanation: an inner declaration shadows outer ones with the same name. Each scope sees its nearest declaration.
19. Variables copy primitives by valueIntermediate
Copy a number into another variable, change the copy and print both.
Show solution
let originalTimeout = 5000;
let copiedTimeout = originalTimeout;
copiedTimeout = 10000;
console.log('original:', originalTimeout);
console.log('copy:', copiedTimeout);
Output
original: 5000
copy: 10000
Explanation: primitives such as numbers are copied by value, so changing the copy does not affect the original.
20. Variables share objects by referenceIntermediate
Assign an object to a second variable, change it through the second variable and print the first.
Show solution
const user = { name: 'Asha', active: true };
const sameUser = user;
sameUser.active = false;
console.log(user.active);
console.log(user === sameUser);
Output
false
true
Explanation: both variables point to the same object, so a change through either one is visible through both.
JavaScript Real-World QA Scenario Programs
21. Store environment URLsReal-world
Store base URLs for qa, staging and prod in constants, pick one with a selectedEnv variable and print the login URL.
Show solution
const QA_URL = 'https://qa.shop.test';
const STAGING_URL = 'https://staging.shop.test';
const PROD_URL = 'https://shop.test';
const selectedEnv = 'staging';
const baseUrl = selectedEnv === 'qa' ? QA_URL : selectedEnv === 'staging' ? STAGING_URL : PROD_URL;
const loginUrl = `${baseUrl}/login`;
console.log(loginUrl);
Output
https://staging.shop.test/login
Explanation: constants keep fixed values in one place, and a single variable decides which environment the test uses.
22. Build unique test dataReal-world
Create a unique email for a sign-up test using a fixed runId variable and a username prefix.
Show solution
const prefix = 'qa.user';
const runId = 1730275200;
const email = `${prefix}+${runId}@example.com`;
console.log(email);
Output
qa.user+1730275200@example.com
Explanation: combining a stable prefix with a run ID gives unique but readable test data. In real tests the ID could come from Date.now().
23. Count test resultsReal-world
Use counter variables to tally the statuses ['passed', 'failed', 'passed', 'skipped', 'passed'].
Show solution
const statuses = ['passed', 'failed', 'passed', 'skipped', 'passed'];
let passed = 0, failed = 0, skipped = 0;
for (const status of statuses) {
if (status === 'passed') passed++;
else if (status === 'failed') failed++;
else skipped++;
}
console.log(`Passed: ${passed}, Failed: ${failed}, Skipped: ${skipped}`);
Output
Passed: 3, Failed: 1, Skipped: 1
Explanation: counters declared with let are updated inside the loop. The loop variable status is a fresh const each time.
24. Calculate a pass percentageReal-world
With total = 80 and failed = 6, calculate and print the pass percentage rounded to one decimal place.
Show solution
const total = 80;
const failed = 6;
const passed = total - failed;
const passRate = (passed / total) * 100;
console.log(`Pass rate: ${passRate.toFixed(1)}%`);
Output
Pass rate: 92.5%
Explanation: intermediate variables like passed make the calculation readable. toFixed(1) formats the result.
25. Default timeout with fallbackReal-world
A variable customTimeout may be undefined. Use ?? to fall back to a default of 30000 ms and print the timeout used in both cases.
Show solution
const DEFAULT_TIMEOUT = 30000;
let customTimeout;
console.log('Timeout used:', customTimeout ?? DEFAULT_TIMEOUT);
customTimeout = 60000;
console.log('Timeout used:', customTimeout ?? DEFAULT_TIMEOUT);
Output
Timeout used: 30000
Timeout used: 60000
Explanation: ?? returns the right side only when the left side is null or undefined, which makes it ideal for optional settings.
26. Track login attemptsReal-world
Simulate three failed login attempts with a let counter and lock the account when it reaches MAX_ATTEMPTS = 3.
Show solution
const MAX_ATTEMPTS = 3;
let attempts = 0;
let locked = false;
for (let i = 1; i <= 3; i++) {
attempts++;
console.log(`Attempt ${attempts} failed`);
if (attempts === MAX_ATTEMPTS) locked = true;
}
console.log('Account locked:', locked);
Output
Attempt 1 failed
Attempt 2 failed
Attempt 3 failed
Account locked: true
Explanation: a constant holds the business rule and let variables track state that changes during the scenario.
27. Build a test report lineReal-world
Store the test name, duration in milliseconds and status in variables, then print one formatted report line with the duration in seconds.
Show solution
const testName = 'Checkout with saved card';
const durationMs = 2345;
const status = 'PASSED';
const durationSec = (durationMs / 1000).toFixed(2);
console.log(`[${status}] ${testName} (${durationSec}s)`);
Output
[PASSED] Checkout with saved card (2.35s)
Explanation: separate variables for each piece of data keep the formatting step simple and easy to change.
28. Running cart totalReal-world
Add item prices 499, 1299 and 250 to a running cartTotal, printing the total after each item.
Show solution
let cartTotal = 0;
cartTotal += 499;
console.log('After item 1:', cartTotal);
cartTotal += 1299;
console.log('After item 2:', cartTotal);
cartTotal += 250;
console.log('After item 3:', cartTotal);
Output
After item 1: 499
After item 2: 1798
After item 3: 2048
Explanation: += updates the running total in place, which mirrors how a test checks a cart after each step.
29. Mask a password before loggingReal-world
Store a password in a variable and log a masked version that shows only the last two characters.
Show solution
const password = 'Secr3t!Pass';
const masked = '*'.repeat(password.length - 2) + password.slice(-2);
console.log('Password used:', masked);
Output
Password used: *********ss
Explanation: never log real secrets. A derived variable holds a safe version for logs and reports.
30. Read settings with fallbacksReal-world
Given envVars = { BASE_URL: 'https://qa.app.test' }, read BASE_URL and WORKERS into variables, defaulting WORKERS to 2.
Show solution
const envVars = { BASE_URL: 'https://qa.app.test' };
const baseUrl = envVars.BASE_URL ?? 'http://localhost:3000';
const workers = Number(envVars.WORKERS ?? 2);
console.log('baseUrl:', baseUrl);
console.log('workers:', workers);
Output
baseUrl: https://qa.app.test
workers: 2
Explanation: tests often read process.env, where values may be missing. Defaults keep the run predictable.
31. Destructure test data into variablesReal-world
Unpack username, password and an optional role (default 'viewer') from a test-data object.
Show solution
const testUser = { username: 'qa_tester', password: 'P@ss123' };
const { username, password, role = 'viewer' } = testUser;
console.log(username, role);
console.log('Password length:', password.length);
Output
qa_tester viewer
Password length: 7
Explanation: object destructuring creates several variables at once and can supply defaults for missing properties.
32. Rename while destructuringReal-world
An API returns { id: 7, full_name: 'Neha Sharma' }. Store the values in camelCase variables userId and fullName.
Show solution
const apiUser = { id: 7, full_name: 'Neha Sharma' };
const { id: userId, full_name: fullName } = apiUser;
console.log(`User #${userId}: ${fullName}`);
Output
User #7: Neha Sharma
Explanation: property: newName in destructuring renames a value, which keeps your code in camelCase even when an API uses snake_case.