Practise TypeScript loops with 30 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 TypeScript for Playwright.
How to practise: read the task, write your own solution first, then open Show solution to compare. Run any program with npx tsx program.ts (or compile it with npx tsc).
New to this topic? Read the TypeScript Loops lesson first.
- Beginner programs (1–10)
- Intermediate programs (11–20)
- Real-world QA scenario programs (21–30)
TypeScript Beginner Programs
1. Sum 1 to n with a typed for loopBeginner
Write sumTo(n: number): number using a for loop.
Show solution
function sumTo(n: number): number {
let total = 0;
for (let i = 1; i <= n; i++) total += i;
return total;
}
console.log(sumTo(10), sumTo(100));
Output
55 5050
Explanation: the loop counter and accumulator are inferred as number, and the return type documents the result.
2. Loop over a typed arrayBeginner
Print each browser from a string[] with for...of.
Show solution
const browsers: string[] = ['chromium', 'firefox', 'webkit'];
for (const browser of browsers) {
console.log(`Run on ${browser.toUpperCase()}`);
}
Output
Run on CHROMIUM
Run on FIREFOX
Run on WEBKIT
Explanation: for...of infers the item type, here string, so string methods are available.
3. Countdown with whileBeginner
Count down from 3 to 1 with a while loop.
Show solution
let remaining: number = 3;
while (remaining > 0) {
console.log(remaining);
remaining--;
}
console.log('Liftoff');
Output
3
2
1
Liftoff
Explanation: a while loop repeats while its condition is true. Update the variable so it ends.
4. Multiplication tableBeginner
Print the 5 times table with a typed helper.
Show solution
function tableLine(n: number, i: number): string {
return `${n} x ${i} = ${n * i}`;
}
for (let i = 1; i <= 5; i++) console.log(tableLine(5, i));
Output
5 x 1 = 5
5 x 2 = 10
5 x 3 = 15
5 x 4 = 20
5 x 5 = 25
Explanation: a small typed helper keeps the loop body short and the formatting reusable.
5. Index and value with entriesBeginner
Print numbered test steps using entries().
Show solution
const steps: string[] = ['open page', 'login', 'add to cart'];
for (const [i, step] of steps.entries()) console.log(`${i + 1}. ${step}`);
Output
1. open page
2. login
3. add to cart
Explanation: entries() yields typed [number, string] pairs that destructure cleanly.
6. Loop over object keys safelyBeginner
Print a typed config with Object.entries.
Show solution
const config: Record<string, string | number> = { browser: 'chromium', workers: 4, retries: 1 };
for (const [key, value] of Object.entries(config)) console.log(`${key}=${value}`);
Output
browser=chromium
workers=4
retries=1
Explanation: Object.entries returns [string, value][], which avoids the indexing errors you get with for...in.
7. Even numbers into a typed arrayBeginner
Collect even numbers up to 12 into a number[].
Show solution
const evens: number[] = [];
for (let n = 2; n <= 12; n += 2) evens.push(n);
console.log(evens.join(', '));
Output
2, 4, 6, 8, 10, 12
Explanation: the array’s type is declared up front, so only numbers can be pushed.
8. do…while for at least one attemptBeginner
Try an action at least once with do...while.
Show solution
let attempt: number = 0;
let success: boolean = false;
do {
attempt++;
success = attempt >= 2;
console.log(`attempt ${attempt}: ${success ? 'ok' : 'retry'}`);
} while (!success && attempt < 5);
Output
attempt 1: retry
attempt 2: ok
Explanation: do...while checks the condition after the body, so the action always runs once.
9. Characters of a stringBeginner
Count uppercase letters in a string.
Show solution
const text: string = 'Playwright Test Runner';
let upper = 0;
for (const ch of text) if (ch >= 'A' && ch <= 'Z') upper++;
console.log('Uppercase letters:', upper);
Output
Uppercase letters: 3
Explanation: strings are iterable, and each ch is typed as string.
10. break and continueBeginner
Print odd numbers below 10 and stop when a number above 7 is reached.
Show solution
const odd: number[] = [];
for (let i = 1; i < 10; i++) {
if (i % 2 === 0) continue;
if (i > 7) break;
odd.push(i);
}
console.log(odd.join(' '));
Output
1 3 5 7
Explanation: continue skips even numbers and break leaves the loop once the limit is passed.
TypeScript Intermediate Programs
11. Loop over a union array with narrowingIntermediate
Sum the numbers in a mixed array and count the strings.
Show solution
const values: (number | string)[] = [10, 'ten', 20, 'twenty', 5];
let sum = 0, strings = 0;
for (const v of values) {
if (typeof v === 'number') sum += v;
else strings++;
}
console.log(`sum=${sum}, strings=${strings}`);
Output
sum=35, strings=2
Explanation: typeof narrows each item, so += is only used on numbers.
12. Loop over a typed object arrayIntermediate
Print users who are active, using an interface for the array items.
Show solution
interface User { name: string; active: boolean; role: 'admin' | 'viewer' }
const users: User[] = [
{ name: 'Asha', active: true, role: 'admin' },
{ name: 'Ravi', active: false, role: 'viewer' },
{ name: 'Meera', active: true, role: 'viewer' }
];
for (const u of users) {
if (!u.active) continue;
console.log(`${u.name} (${u.role})`);
}
Output
Asha (admin)
Meera (viewer)
Explanation: the interface gives every loop item a known shape, so property typos are compile errors.
13. Typed accumulator with RecordIntermediate
Count statuses into a Record<Status, number>.
Show solution
type Status = 'passed' | 'failed' | 'skipped';
const results: Status[] = ['passed', 'failed', 'passed', 'skipped', 'passed'];
const counts: Record<Status, number> = { passed: 0, failed: 0, skipped: 0 };
for (const r of results) counts[r]++;
console.log(counts);
Output
{ passed: 3, failed: 1, skipped: 1 }
Explanation: the record type guarantees a counter exists for every status.
14. Nested loops for combinationsIntermediate
Build every browser and viewport combination.
Show solution
const browsers: string[] = ['chromium', 'webkit'];
const viewports: [number, number][] = [[1280, 720], [375, 812]];
const combos: string[] = [];
for (const b of browsers) {
for (const [w, h] of viewports) combos.push(`${b}@${w}x${h}`);
}
console.log(combos.join(', '));
Output
chromium@1280x720, chromium@375x812, webkit@1280x720, webkit@375x812
Explanation: tuple types make it clear each viewport has exactly a width and a height.
15. Labeled breakIntermediate
Find the first cell with a negative value in a grid.
Show solution
const grid: number[][] = [[3, 5, 2], [7, -1, 4], [-6, 8, 9]];
let found: [number, number] | null = null;
search: for (let r = 0; r < grid.length; r++) {
for (let c = 0; c < grid[r].length; c++) {
if (grid[r][c] < 0) {
found = [r, c];
break search;
}
}
}
console.log(found);
Output
[ 1, 1 ]
Explanation: the label lets break exit both loops at once. The result type allows null when nothing is found.
16. Loop with a type guardIntermediate
Collect only valid emails from unknown input.
Show solution
const isString = (v: unknown): v is string => typeof v === 'string';
const input: unknown[] = ['qa@test.com', 42, 'bad-email', null, 'dev@shop.in'];
const emails: string[] = [];
for (const v of input) {
if (isString(v) && v.includes('@')) emails.push(v);
}
console.log(emails.join(', '));
Output
qa@test.com, dev@shop.in
Explanation: the type guard narrows unknown to string, so includes is allowed.
17. Fibonacci into a typed arrayIntermediate
Generate the first 10 Fibonacci numbers.
Show solution
function fibonacci(count: number): number[] {
const seq: number[] = [0, 1];
while (seq.length < count) seq.push(seq[seq.length - 1] + seq[seq.length - 2]);
return seq.slice(0, count);
}
console.log(fibonacci(10).join(' '));
Output
0 1 1 2 3 5 8 13 21 34
Explanation: the return type number[] documents the result, and slice handles counts below 2.
18. Prime numbers up to nIntermediate
Return all primes up to 30.
Show solution
function primesUpTo(n: number): number[] {
const primes: number[] = [];
for (let i = 2; i <= n; i++) {
let isPrime = true;
for (const p of primes) {
if (p * p > i) break;
if (i % p === 0) { isPrime = false; break; }
}
if (isPrime) primes.push(i);
}
return primes;
}
console.log(primesUpTo(30).join(' '));
Output
2 3 5 7 11 13 17 19 23 29
Explanation: dividing only by primes up to √i keeps the inner loop short.
19. Iterate a MapIntermediate
Loop over a Map<string, number> of response times.
Show solution
const timings = new Map<string, number>([['/login', 220], ['/cart', 480], ['/search', 130]]);
let slowest: [string, number] = ['', 0];
for (const [url, ms] of timings) {
if (ms > slowest[1]) slowest = [url, ms];
}
console.log(`Slowest: ${slowest[0]} (${slowest[1]} ms)`);
Output
Slowest: /cart (480 ms)
Explanation: iterating a Map yields typed [key, value] pairs.
20. Reverse a string with a loopIntermediate
Reverse a string without using reverse().
Show solution
function reverse(text: string): string {
let out = '';
for (let i = text.length - 1; i >= 0; i--) out += text[i];
return out;
}
console.log(reverse('TypeScript'));
Output
tpircSepyT
Explanation: counting down from the last index visits characters in reverse order.
TypeScript Real-World QA Scenario Programs
21. Retry until successReal-world
Retry a typed action until it succeeds or the attempts run out.
Show solution
type ActionResult = { ok: boolean; message: string };
let calls = 0;
const clickPay = (): ActionResult => (++calls < 3 ? { ok: false, message: 'button disabled' } : { ok: true, message: 'paid' });
const MAX = 5;
let result: ActionResult = { ok: false, message: 'not started' };
for (let attempt = 1; attempt <= MAX && !result.ok; attempt++) {
result = clickPay();
console.log(`attempt ${attempt}: ${result.message}`);
}
Output
attempt 1: button disabled
attempt 2: button disabled
attempt 3: paid
Explanation: the loop condition combines the attempt limit and the success flag, so it stops as soon as either is reached.
22. Poll a job statusReal-world
Poll a typed status sequence until it is done or failed.
Show solution
type JobStatus = 'queued' | 'running' | 'done' | 'failed';
const sequence: JobStatus[] = ['queued', 'running', 'running', 'done'];
let poll = 0;
let status: JobStatus = 'queued';
while (status !== 'done' && status !== 'failed' && poll < sequence.length) {
status = sequence[poll++];
console.log(`poll ${poll}: ${status}`);
}
Output
poll 1: queued
poll 2: running
poll 3: running
poll 4: done
Explanation: the literal union makes the stop conditions explicit and typo-proof.
23. Summarise test resultsReal-world
Calculate pass rate and total duration from typed results.
Show solution
interface TestResult { title: string; status: 'passed' | 'failed'; ms: number }
const results: TestResult[] = [
{ title: 'login', status: 'passed', ms: 1200 },
{ title: 'search', status: 'failed', ms: 3400 },
{ title: 'checkout', status: 'passed', ms: 5100 }
];
let passed = 0, totalMs = 0;
for (const r of results) {
if (r.status === 'passed') passed++;
totalMs += r.ms;
}
console.log(`${passed}/${results.length} passed in ${(totalMs / 1000).toFixed(1)}s`);
Output
2/3 passed in 9.7s
Explanation: a single typed loop gathers several statistics at once.
24. Find the first failureReal-world
Return the first failed test or undefined.
Show solution
interface Run { name: string; passed: boolean }
function firstFailure(runs: Run[]): Run | undefined {
for (const run of runs) if (!run.passed) return run;
return undefined;
}
const runs: Run[] = [{ name: 'login', passed: true }, { name: 'cart', passed: false }];
console.log(firstFailure(runs)?.name ?? 'all passed');
console.log(firstFailure([{ name: 'login', passed: true }])?.name ?? 'all passed');
Output
cart
all passed
Explanation: returning inside the loop stops at the first match. The | undefined return type forces callers to handle “not found”.
25. Cart total from typed itemsReal-world
Calculate a cart total and the most expensive line.
Show solution
interface CartLine { sku: string; price: number; qty: number }
const cart: CartLine[] = [{ sku: 'KB', price: 1499, qty: 1 }, { sku: 'MS', price: 599, qty: 2 }, { sku: 'CB', price: 199, qty: 3 }];
let total = 0;
let top: CartLine = cart[0];
for (const line of cart) {
total += line.price * line.qty;
if (line.price * line.qty > top.price * top.qty) top = line;
}
console.log('Total:', total, '| Biggest line:', top.sku);
Output
Total: 3294 | Biggest line: KB
Explanation: typed lines guarantee numeric prices and quantities for the arithmetic.
26. Generate typed test usersReal-world
Generate a list of typed test users with a counter loop.
Show solution
interface TestUser { id: number; email: string; role: 'admin' | 'viewer' }
const users: TestUser[] = [];
for (let i = 1; i <= 4; i++) {
users.push({ id: i, email: `qa${i}@example.com`, role: i === 1 ? 'admin' : 'viewer' });
}
console.log(users.map(u => `${u.id}:${u.role}`).join(' '));
Output
1:admin 2:viewer 3:viewer 4:viewer
Explanation: pushing objects into a typed array checks each one against the interface.
27. Validate every form fieldReal-world
Loop over typed field rules and collect validation errors.
Show solution
interface Rule { field: string; value: string; minLength: number }
const rules: Rule[] = [
{ field: 'username', value: 'qa', minLength: 3 },
{ field: 'password', value: 'Secret123', minLength: 8 },
{ field: 'city', value: '', minLength: 1 }
];
const errors: string[] = [];
for (const r of rules) {
if (r.value.trim().length < r.minLength) errors.push(`${r.field} needs ${r.minLength}+ characters`);
}
console.log(errors.length ? errors.join('; ') : 'All valid');
Output
username needs 3+ characters; city needs 1+ characters
Explanation: data-driven rules keep validation logic in one loop instead of repeated if blocks.
28. Paginate until an empty pageReal-world
Read typed pages until the API returns an empty list.
Show solution
const pages: string[][] = [['a', 'b'], ['c', 'd'], ['e'], []];
let page = 0;
const all: string[] = [];
while (true) {
const items = pages[page];
if (items.length === 0) break;
all.push(...items);
page++;
}
console.log(`${page} pages, ${all.length} items: ${all.join('')}`);
Output
3 pages, 5 items: abcde
Explanation: while (true) with a clear break condition is a common pattern for pagination.
29. Compare expected and actual listsReal-world
Report missing and unexpected menu items with typed arrays.
Show solution
const expected: readonly string[] = ['Home', 'Shop', 'Blog', 'Contact'];
const actual: string[] = ['Home', 'Shop', 'Offers', 'Contact'];
const missing: string[] = [];
const extra: string[] = [];
for (const item of expected) if (!actual.includes(item)) missing.push(item);
for (const item of actual) if (!expected.includes(item)) extra.push(item);
console.log('missing:', missing.join(', ') || '-', '| extra:', extra.join(', ') || '-');
Output
missing: Blog | extra: Offers
Explanation: two simple loops produce a clear diff, which is easier to debug than a single equality check.
30. Run async steps in orderReal-world
Run typed async steps sequentially with for...of and await.
Show solution
type Step = { name: string; ms: number };
const sleep = (ms: number): Promise<void> => new Promise(resolve => setTimeout(resolve, ms));
const steps: Step[] = [{ name: 'open', ms: 20 }, { name: 'fill', ms: 5 }, { name: 'submit', ms: 10 }];
const done: string[] = [];
for (const step of steps) {
await sleep(step.ms);
done.push(step.name);
}
console.log(done.join(' -> '));
Output
open -> fill -> submit
Explanation: await inside for...of keeps the steps in order, the same way Playwright actions run one after another.
1 thought on “TypeScript Loops Practice Programs with Solutions (30 Programs)”