TypeScript Arrays and Tuples Practice Programs with Solutions (31 Programs)

Practise TypeScript arrays and tuples with 31 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 Arrays lesson first.

TypeScript Beginner Programs

1. Declare typed arraysBeginner

Declare a string[] of browsers and a number[] of timeouts, then print their lengths.

Show solution
const browsers: string[] = ['chromium', 'firefox', 'webkit'];
const timeouts: number[] = [5000, 10000, 30000];
console.log(browsers.length, timeouts.length);
console.log(browsers[0], timeouts[2]);

Output

3 3
chromium 30000

Explanation: the element type is fixed by the annotation, so every item is checked when the array is created.

2. Wrong element types are compile errorsBeginner

Show that pushing a number into a string[] is rejected.

Show solution
const tags: string[] = ['@smoke'];
tags.push('@regression');
// @ts-expect-error: Argument of type 'number' is not assignable to parameter of type 'string'
tags.push(42);
console.log(tags.length);

Output

3

Explanation: the compiler stops the bad push. The line still runs here for the demo, which is why the length is 3; in a real build the error would stop you first.

3. Array<T> generic syntaxBeginner

Declare the same kind of array with Array&lt;number&gt; and sum it.

Show solution
const scores: Array<number> = [90, 75, 88];
let total = 0;
for (const s of scores) total += s;
console.log(`total ${total}, average ${(total / scores.length).toFixed(1)}`);

Output

total 253, average 84.3

Explanation: Array&lt;number&gt; and number[] mean exactly the same thing. Most code uses number[]; the generic form reads better for complex types.

4. Inferred array typeBeginner

Let TypeScript infer the type of an array literal and use a string method on an item.

Show solution
const pages = ['home', 'login', 'cart'];
const upper = pages.map(p => p.toUpperCase());
console.log(upper.join(' | '));

Output

HOME | LOGIN | CART

Explanation: the literal is inferred as string[], so p is a string inside map without any annotation.

5. Union element typeBeginner

Store test ids that may be numbers or strings and print each with its type.

Show solution
const ids: (string | number)[] = [101, 'TC-202', 303];
for (const id of ids) console.log(`${id} is ${typeof id}`);

Output

101 is number
TC-202 is string
303 is number

Explanation: the parentheses matter: (string | number)[] is an array of mixed items, while string | number[] would mean a string or an array of numbers.

6. Readonly arrayBeginner

Create a readonly string[] of environments and show that it cannot be changed.

Show solution
const envs: readonly string[] = ['dev', 'staging', 'prod'];
console.log(envs.includes('staging'));
// @ts-expect-error: Property 'push' does not exist on type 'readonly string[]'
const add = () => envs.push('qa');
console.log(envs.length, typeof add);

Output

true
3 function

Explanation: a readonly array has no mutating methods in its type, so push, pop and index assignment fail to compile.

7. Tuple for a key-value pairBeginner

Model a header as a [string, string] tuple and print it.

Show solution
const header: [string, string] = ['Content-Type', 'application/json'];
const [name, value] = header;
console.log(`${name}: ${value}`);

Output

Content-Type: application/json

Explanation: a tuple fixes both the length and the type at each position, and destructuring keeps those types.

8. Tuple with mixed typesBeginner

Store a test result as [string, boolean, number].

Show solution
const result: [string, boolean, number] = ['login test', true, 1240];
const [title, passed, ms] = result;
console.log(`${title}: ${passed ? 'PASS' : 'FAIL'} in ${ms}ms`);

Output

login test: PASS in 1240ms

Explanation: each position has its own type, so passed is a boolean and ms is a number after destructuring.

9. Filter with a typed callbackBeginner

Keep only the failed tests from a list of statuses.

Show solution
const statuses: string[] = ['passed', 'failed', 'passed', 'skipped', 'failed'];
const failed = statuses.filter(s => s === 'failed');
console.log(`${failed.length} failed of ${statuses.length}`);

Output

2 failed of 5

Explanation: filter returns the same element type, string[], so the result needs no annotation.

10. Map numbers to stringsBeginner

Turn durations in milliseconds into labelled seconds.

Show solution
const durations: number[] = [1200, 450, 3100];
const labels: string[] = durations.map(ms => `${(ms / 1000).toFixed(2)}s`);
console.log(labels.join(', '));

Output

1.20s, 0.45s, 3.10s

Explanation: map changes the element type from number to string, and the annotation on labels confirms it.

TypeScript Intermediate Programs

11. reduce with a typed accumulatorIntermediate

Total the run time of tests with reduce.

Show solution
const runs: number[] = [320, 1500, 780, 95];
const totalMs = runs.reduce((sum: number, ms: number) => sum + ms, 0);
console.log(`total ${totalMs}ms`);

Output

total 2695ms

Explanation: the starting value 0 gives the accumulator its number type, so the result is a number.

12. find returns T | undefinedIntermediate

Find a user by role and handle the case where none matches.

Show solution
const users: string[] = ['admin@test.io', 'viewer@test.io'];
const editor = users.find(u => u.startsWith('editor'));
console.log(editor ?? 'no editor found');
const admin = users.find(u => u.startsWith('admin'));
if (admin) console.log(admin.toUpperCase());

Output

no editor found
ADMIN@TEST.IO

Explanation: find returns string | undefined, so TypeScript makes you check the result before calling string methods on it.

13. Array of objectsIntermediate

Type a list of test cases and print the ones tagged as smoke tests.

Show solution
type TestCase = { id: number; title: string; tags: string[] };
const cases: TestCase[] = [
  { id: 1, title: 'login', tags: ['smoke'] },
  { id: 2, title: 'search', tags: ['regression'] },
  { id: 3, title: 'checkout', tags: ['smoke', 'payments'] },
];
const smoke = cases.filter(c => c.tags.includes('smoke')).map(c => c.title);
console.log(smoke.join(', '));

Output

login, checkout

Explanation: once the element type is named, every callback knows the shape of c, and misspelled properties fail to compile.

14. Sort numbers correctlyIntermediate

Sort response times in ascending order.

Show solution
const times: number[] = [250, 1200, 90, 400];
const sorted = [...times].sort((a, b) => a - b);
console.log(sorted.join(' < '));
console.log(times.join(', '));

Output

90 < 250 < 400 < 1200
250, 1200, 90, 400

Explanation: without a compare function sort compares as strings, so 1200 would come before 250. Copying with spread keeps the original untouched.

15. Sort objects by a propertyIntermediate

Sort test results from slowest to fastest.

Show solution
type Result = { name: string; ms: number };
const results: Result[] = [
  { name: 'login', ms: 820 },
  { name: 'search', ms: 2100 },
  { name: 'logout', ms: 300 },
];
results.sort((a, b) => b.ms - a.ms);
console.log(results.map(r => `${r.name}:${r.ms}`).join(' '));

Output

search:2100 login:820 logout:300

Explanation: the compare function is typed from Result, so a.ms and b.ms are numbers.

16. Remove duplicates with SetIntermediate

Remove duplicate tags while keeping a string[] type.

Show solution
const tags: string[] = ['smoke', 'ui', 'smoke', 'api', 'ui'];
const unique: string[] = [...new Set(tags)];
console.log(unique.join(', '));

Output

smoke, ui, api

Explanation: new Set(tags) is a Set&lt;string&gt;, and spreading it back gives a string[].

17. Type guard in filterIntermediate

Remove null values from a list and get a string[] back.

Show solution
const raw: (string | null)[] = ['a@test.io', null, 'b@test.io', null];
const emails = raw.filter((e): e is string => e !== null);
console.log(emails.map(e => e.toUpperCase()).join(', '));

Output

A@TEST.IO, B@TEST.IO

Explanation: the e is string predicate tells TypeScript the result is string[]. Without it the type would stay (string | null)[].

18. Optional tuple elementIntermediate

Model a viewport as [width, height, scale?].

Show solution
type Viewport = [number, number, number?];
const desktop: Viewport = [1280, 720];
const retina: Viewport = [1440, 900, 2];
for (const [w, h, scale = 1] of [desktop, retina]) console.log(`${w}x${h} @${scale}x`);

Output

1280x720 @1x
1440x900 @2x

Explanation: the ? makes the last position optional, and a default in the destructuring fills it in.

19. Named tuple elementsIntermediate

Use labelled tuple members to describe a retry setting.

Show solution
type RetryPolicy = [attempts: number, delayMs: number];
const policy: RetryPolicy = [3, 500];
const [attempts, delayMs] = policy;
console.log(`retry ${attempts} times every ${delayMs}ms`);

Output

retry 3 times every 500ms

Explanation: labels do not change the runtime value, but they show up in editor hints, which makes tuples self-documenting.

20. Readonly tuple with as constIntermediate

Freeze a list of browsers with as const and use it as a union type.

Show solution
const BROWSERS = ['chromium', 'firefox', 'webkit'] as const;
type Browser = (typeof BROWSERS)[number];
function launch(b: Browser): string {
  return `launching ${b}`;
}
console.log(launch('firefox'));
console.log(BROWSERS.length);

Output

launching firefox
3

Explanation: as const makes a readonly tuple of literal types, and [number] turns it into the union 'chromium' | 'firefox' | 'webkit'.

21. Rest elements in a tupleIntermediate

Type a log line as a level followed by any number of messages.

Show solution
type LogLine = [level: 'info' | 'error', ...messages: string[]];
const lines: LogLine[] = [
  ['info', 'page loaded'],
  ['error', 'timeout', 'retrying'],
];
for (const [level, ...msgs] of lines) console.log(`[${level}] ${msgs.join(' / ')}`);

Output

[info] page loaded
[error] timeout / retrying

Explanation: a rest element lets a tuple have a fixed start and a variable tail, still fully typed.

22. Two-dimensional arrayIntermediate

Store a small test data grid as string[][] and print it.

Show solution
const grid: string[][] = [
  ['user', 'role'],
  ['ana', 'admin'],
  ['ben', 'viewer'],
];
for (const row of grid) console.log(row.join(' | '));
console.log(`rows ${grid.length}, cols ${grid[0].length}`);

Output

user | role
ana | admin
ben | viewer
rows 3, cols 2

Explanation: string[][] is an array whose items are string[], so each row has array methods.

TypeScript Real-World QA Scenario Programs

23. Group items into a recordReal-world

Group test names by status into Record&lt;string, string[]&gt;.

Show solution
type Run = { name: string; status: 'passed' | 'failed' | 'skipped' };
const runs: Run[] = [
  { name: 'login', status: 'passed' },
  { name: 'cart', status: 'failed' },
  { name: 'search', status: 'passed' },
  { name: 'profile', status: 'skipped' },
];
const byStatus: Record<string, string[]> = {};
for (const r of runs) (byStatus[r.status] ??= []).push(r.name);
for (const [status, names] of Object.entries(byStatus)) console.log(`${status}: ${names.join(', ')}`);

Output

passed: login, search
failed: cart
skipped: profile

Explanation: ??= creates the list the first time a status appears, and the record type keeps every value a string[].

24. Chunk an array genericallyReal-world

Split a list of test files into batches of a given size.

Show solution
function chunk<T>(items: T[], size: number): T[][] {
  const out: T[][] = [];
  for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));
  return out;
}
const files = ['a.spec', 'b.spec', 'c.spec', 'd.spec', 'e.spec'];
chunk(files, 2).forEach((batch, i) => console.log(`worker ${i + 1}: ${batch.join(', ')}`));

Output

worker 1: a.spec, b.spec
worker 2: c.spec, d.spec
worker 3: e.spec

Explanation: the generic T means the same function chunks strings, numbers or objects, and the result keeps the element type.

25. Generic last-item helperReal-world

Write last&lt;T&gt;(items: T[]): T | undefined.

Show solution
function last<T>(items: readonly T[]): T | undefined {
  return items[items.length - 1];
}
console.log(last(['step 1', 'step 2', 'step 3']));
console.log(last([10, 20]));
console.log(last([]) ?? 'empty');

Output

step 3
20
empty

Explanation: the return type includes undefined because the array may be empty. Taking readonly T[] lets callers pass frozen arrays too.

26. Zip two arrays into tuplesReal-world

Pair test names with their durations.

Show solution
function zip<A, B>(a: A[], b: B[]): [A, B][] {
  const n = Math.min(a.length, b.length);
  const out: [A, B][] = [];
  for (let i = 0; i < n; i++) out.push([a[i], b[i]]);
  return out;
}
const pairs = zip(['login', 'search', 'cart'], [820, 2100, 640]);
for (const [name, ms] of pairs) console.log(`${name.padEnd(7)}${ms}ms`);

Output

login  820ms
search 2100ms
cart   640ms

Explanation: two type parameters keep each side’s type, so name is a string and ms is a number in the loop.

27. Partition into pass and failReal-world

Split results into two arrays in one pass, returning a tuple.

Show solution
function partition<T>(items: T[], test: (item: T) => boolean): [T[], T[]] {
  const yes: T[] = [];
  const no: T[] = [];
  for (const item of items) (test(item) ? yes : no).push(item);
  return [yes, no];
}
const codes = [200, 404, 201, 500, 204];
const [ok, bad] = partition(codes, c => c < 400);
console.log(`ok: ${ok.join(',')} | bad: ${bad.join(',')}`);

Output

ok: 200,201,204 | bad: 404,500

Explanation: returning a tuple [T[], T[]] lets the caller destructure both groups with the right types.

28. Count by key with a MapReal-world

Count how many times each error message appears.

Show solution
const errors: string[] = ['timeout', 'not found', 'timeout', 'timeout', 'not found', 'crash'];
const counts = new Map<string, number>();
for (const e of errors) counts.set(e, (counts.get(e) ?? 0) + 1);
const top = [...counts].sort((a, b) => b[1] - a[1]);
for (const [msg, n] of top) console.log(`${n} x ${msg}`);

Output

3 x timeout
2 x not found
1 x crash

Explanation: Map&lt;string, number&gt; keeps keys and counts typed, and spreading a map gives [string, number][] for sorting.

29. Flatten nested arraysReal-world

Flatten a list of test suites into one list of tests.

Show solution
const suites: string[][] = [['login', 'logout'], ['search'], ['cart', 'checkout']];
const tests: string[] = suites.flat();
console.log(`${tests.length} tests: ${tests.join(', ')}`);
const steps = suites.flatMap(s => s.map(t => `run ${t}`));
console.log(steps[0], '...', steps[steps.length - 1]);

Output

5 tests: login, logout, search, cart, checkout
run login ... run checkout

Explanation: flat() on string[][] returns string[], and flatMap maps and flattens one level in a single call.

30. Pass rate per suiteReal-world

Compute the pass rate for each suite from a typed array of results.

Show solution
type Outcome = { suite: string; passed: boolean };
const outcomes: Outcome[] = [
  { suite: 'auth', passed: true },
  { suite: 'auth', passed: false },
  { suite: 'cart', passed: true },
  { suite: 'cart', passed: true },
];
const stats = new Map<string, [pass: number, total: number]>();
for (const o of outcomes) {
  const [pass, total] = stats.get(o.suite) ?? [0, 0];
  stats.set(o.suite, [pass + (o.passed ? 1 : 0), total + 1]);
}
for (const [suite, [pass, total]] of stats) console.log(`${suite}: ${Math.round((pass / total) * 100)}%`);

Output

auth: 50%
cart: 100%

Explanation: storing a named tuple in the map keeps two related numbers together with clear labels.

31. Readonly parameter protects the callerReal-world

Write a function that returns a sorted copy without changing a readonly input.

Show solution
function sortedCopy(values: readonly number[]): number[] {
  // @ts-expect-error: Property 'sort' does not exist on type 'readonly number[]'
  const misuse = () => values.sort();
  return [...values].sort((a, b) => a - b);
}
const original = [3, 1, 2] as const;
console.log(sortedCopy(original).join(','), original.join(','));

Output

1,2,3 3,1,2

Explanation: sort changes the array in place, so it is not allowed on a readonly type. Copy first, then sort; the caller’s data stays the same.

Keep Practising

Leave a Comment