TypeScript Loops Practice Programs with Solutions (30 Programs)

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.

Advertisement

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.

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&lt;Status, number&gt;.

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&lt;string, number&gt; 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.

Keep Practising

1 thought on “TypeScript Loops Practice Programs with Solutions (30 Programs)”

Leave a Comment