Practise TypeScript data types 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.
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 Data Types lesson first.
- Beginner programs (1–10)
- Intermediate programs (11–20)
- Real-world QA scenario programs (21–31)
TypeScript Beginner Programs
1. The primitive typesBeginner
Declare one variable of each primitive type and print its runtime type.
Show solution
const title: string = 'Login test';
const duration: number = 2.5;
const passed: boolean = true;
const bigId: bigint = 9007199254740993n;
const key: symbol = Symbol('id');
console.log(typeof title, typeof duration, typeof passed, typeof bigId, typeof key);
Output
string number boolean bigint symbol
Explanation: TypeScript’s primitive types match JavaScript’s runtime types.
2. null and undefinedBeginner
Declare variables that can be null or undefined and print them.
Show solution
let selectedOption: string | null = null;
let middleName: string | undefined;
console.log(selectedOption, middleName);
selectedOption = 'Express';
console.log(selectedOption);
Output
null undefined
Express
Explanation: with strictNullChecks, null and undefined must be included in the type explicitly.
3. Arrays in two notationsBeginner
Declare number arrays with number[] and Array<number>.
Show solution
const scores: number[] = [90, 75, 88];
const timings: Array<number> = [120, 340];
console.log(scores.length + timings.length);
console.log(Math.max(...scores));
Output
5
90
Explanation: number[] and Array<number> are the same type. The shorter form is more common.
4. TupleBeginner
Store a username and password pair in a tuple.
Show solution
const credentials: [string, string] = ['qa_user', 'Secret123'];
const [user, pass] = credentials;
console.log(user, pass.length);
Output
qa_user 9
Explanation: a tuple has a fixed length and a type for each position.
5. Tuple with different typesBeginner
Store an HTTP status code and message as [number, string].
Show solution
const response: [number, string] = [404, 'Not Found'];
console.log(`${response[0]} ${response[1]}`);
// @ts-expect-error: Type 'string' is not assignable to type 'number'
const wrong: [number, string] = ['404', 'Not Found'];
console.log(wrong.length);
Output
404 Not Found
2
Explanation: each tuple position is checked separately, so swapped or wrong types are compile errors.
6. Numeric enumBeginner
Create a Priority enum and print a value and its name.
Show solution
enum Priority { Low, Medium, High }
const p: Priority = Priority.High;
console.log(p);
console.log(Priority[p]);
Output
2
High
Explanation: numeric enums start at 0. Reverse mapping turns a number back into its name.
7. String enumBeginner
Create a Browser string enum and use it in a message.
Show solution
enum Browser {
Chromium = 'chromium',
Firefox = 'firefox',
WebKit = 'webkit'
}
const target: Browser = Browser.Firefox;
console.log(`Running on ${target}`);
Output
Running on firefox
Explanation: string enum members have readable values, which is useful in logs and reports.
8. void return typeBeginner
Write a logging function with a void return type.
Show solution
function logStep(step: string): void {
console.log(`STEP: ${step}`);
}
const result = logStep('Open login page');
console.log(result);
Output
STEP: Open login page
undefined
Explanation: void means the function returns nothing useful. At runtime it returns undefined.
9. Object typeBeginner
Describe a product with an inline object type.
Show solution
const product: { sku: string; price: number; tags: string[] } = {
sku: 'KB-01',
price: 1499,
tags: ['electronics', 'keyboard']
};
console.log(product.sku, product.price, product.tags.join('/'));
Output
KB-01 1499 electronics/keyboard
Explanation: object types list each property name with its type.
10. Date typeBeginner
Create a Date from a fixed ISO string and print parts of it in UTC.
Show solution
const runDate: Date = new Date('2026-11-02T09:30:00Z');
console.log(runDate.toISOString());
console.log(runDate.getUTCFullYear(), runDate.getUTCMonth() + 1, runDate.getUTCDate());
Output
2026-11-02T09:30:00.000Z
2026 11 2
Explanation: built-in classes such as Date are types too. UTC methods avoid time-zone differences.
TypeScript Intermediate Programs
11. unknown vs anyIntermediate
Assign the same value to any and unknown and see which requires a check.
Show solution
const fromApi: unknown = JSON.parse('{"id": 7}');
const anything: any = JSON.parse('{"id": 7}');
console.log(anything.id);
if (typeof fromApi === 'object' && fromApi !== null && 'id' in fromApi) {
console.log((fromApi as { id: number }).id);
}
Output
7
7
Explanation: unknown forces you to check the shape first, while any lets you access anything without checks.
12. never for exhaustive checksIntermediate
Use never to make sure every status in a union is handled.
Show solution
type Status = 'passed' | 'failed' | 'skipped';
function label(status: Status): string {
switch (status) {
case 'passed': return '✔';
case 'failed': return '✘';
case 'skipped': return '-';
default: {
const unreachable: never = status;
return unreachable;
}
}
}
console.log(['passed', 'failed', 'skipped'].map(s => label(s as Status)).join(' '));
Output
✔ ✘ -
Explanation: if a new status is added to the union, the never assignment fails to compile until it is handled.
13. Union of object typesIntermediate
Model an API result that is either success or error, and print it.
Show solution
type ApiResult = { ok: true; data: string[] } | { ok: false; error: string };
const results: ApiResult[] = [{ ok: true, data: ['a', 'b'] }, { ok: false, error: 'Timeout' }];
for (const r of results) {
console.log(r.ok ? `Got ${r.data.length} items` : `Failed: ${r.error}`);
}
Output
Got 2 items
Failed: Timeout
Explanation: checking the ok property narrows the union, so data and error are only accessible in the right branch.
14. Intersection typesIntermediate
Combine a User type and a Timestamps type into one.
Show solution
type User = { id: number; name: string };
type Timestamps = { createdAt: string };
type UserRecord = User & Timestamps;
const rec: UserRecord = { id: 1, name: 'Asha', createdAt: '2026-11-01' };
console.log(rec.name, rec.createdAt);
Output
Asha 2026-11-01
Explanation: A & B requires all properties of both types.
15. Type assertion with asIntermediate
Parse JSON and tell TypeScript its shape with as.
Show solution
type Config = { retries: number; baseURL: string };
const config = JSON.parse('{"retries":2,"baseURL":"https://qa.app.test"}') as Config;
console.log(config.retries + 1, config.baseURL);
Output
3 https://qa.app.test
Explanation: as tells the compiler what type to assume. It does not check the data at runtime, so use it only when you trust the source.
16. Optional tuple elementsIntermediate
Use a tuple with an optional third element for a test step.
Show solution
type Step = [action: string, target: string, value?: string];
const steps: Step[] = [['click', 'Login'], ['fill', 'Email', 'qa@test.com']];
for (const [action, target, value] of steps) {
console.log(value ? `${action} ${target} = ${value}` : `${action} ${target}`);
}
Output
click Login
fill Email = qa@test.com
Explanation: labelled tuple elements document each position, and ? makes the last one optional.
17. Readonly tupleIntermediate
Create a read-only coordinate pair and show the compile error when changing it.
Show solution
const viewport: readonly [number, number] = [1280, 720];
console.log(`${viewport[0]}x${viewport[1]}`);
// @ts-expect-error: Cannot assign to '0' because it is a read-only property
viewport[0] = 1920;
Output
1280x720
Explanation: readonly tuples protect fixed values such as default viewport sizes.
18. Index signaturesIntermediate
Type an object of HTTP headers with unknown keys.
Show solution
const headers: { [name: string]: string } = { 'content-type': 'application/json' };
headers['x-request-id'] = 'abc-123';
console.log(Object.keys(headers).length, headers['content-type']);
Output
2 application/json
Explanation: an index signature allows any string key with values of the declared type.
19. Function typesIntermediate
Declare a variable whose type is a function from string to boolean.
Show solution
type Validator = (value: string) => boolean;
const isEmail: Validator = value => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
const isNotEmpty: Validator = value => value.trim().length > 0;
console.log(isEmail('qa@test.com'), isEmail('qa'), isNotEmpty(' '));
Output
true false false
Explanation: function types describe parameters and return types, so any function assigned must match.
20. bigint arithmeticIntermediate
Add two large order IDs as bigint values.
Show solution
const base: bigint = 9007199254740991n;
const next: bigint = base + 2n;
console.log(next.toString());
try {
// @ts-expect-error: Operator '+' cannot be applied to types 'bigint' and 'number'
console.log(base + 1);
} catch (error) {
console.log('Also fails at runtime:', error instanceof TypeError);
}
Output
9007199254740993
Also fails at runtime: true
Explanation: TypeScript rejects mixing bigint and number at compile time. The same mistake would throw a TypeError in plain JavaScript, so the compiler saves you from a runtime crash.
TypeScript Real-World QA Scenario Programs
21. Typed API responseReal-world
Describe a paginated API response and read its fields.
Show solution
type Product = { id: number; name: string; price: number };
type Page<T> = { items: T[]; page: number; total: number };
const response: Page<Product> = {
items: [{ id: 1, name: 'Mouse', price: 599 }, { id: 2, name: 'Cable', price: 199 }],
page: 1,
total: 12
};
console.log(`Page ${response.page}: ${response.items.length} of ${response.total} items`);
console.log(response.items.map(p => p.name).join(', '));
Output
Page 1: 2 of 12 items
Mouse, Cable
Explanation: a typed response shape gives autocomplete and catches typos such as respose.itmes.
22. Status enum in test resultsReal-world
Use a string enum for test statuses and count them.
Show solution
enum TestStatus { Passed = 'passed', Failed = 'failed', Skipped = 'skipped' }
const runs: TestStatus[] = [TestStatus.Passed, TestStatus.Failed, TestStatus.Passed];
const failed = runs.filter(s => s === TestStatus.Failed).length;
console.log(`${runs.length} runs, ${failed} failed`);
Output
3 runs, 1 failed
Explanation: an enum gives one source of truth for allowed status values.
23. Tuple for table rowsReal-world
Type web-table rows as tuples and total the price column.
Show solution
type Row = [name: string, qty: number, price: number];
const rows: Row[] = [['Pen', 3, 20], ['Book', 1, 350], ['Bag', 2, 899]];
const total = rows.reduce((sum, [, qty, price]) => sum + qty * price, 0);
console.log('Order total:', total);
Output
Order total: 2208
Explanation: tuples fit fixed-column data such as table rows, and destructuring reads the columns by position.
24. Discriminated union for UI eventsReal-world
Handle click and fill events with a discriminated union.
Show solution
type Action =
| { type: 'click'; selector: string }
| { type: 'fill'; selector: string; value: string }
| { type: 'goto'; url: string };
const actions: Action[] = [
{ type: 'goto', url: '/login' },
{ type: 'fill', selector: '#email', value: 'qa@test.com' },
{ type: 'click', selector: 'button[type=submit]' }
];
for (const a of actions) {
if (a.type === 'goto') console.log('Navigate to', a.url);
else if (a.type === 'fill') console.log(`Fill ${a.selector} with ${a.value}`);
else console.log('Click', a.selector);
}
Output
Navigate to /login
Fill #email with qa@test.com
Click button[type=submit]
Explanation: the shared type field tells TypeScript which properties each action has.
25. Validate unknown JSONReal-world
Write a type guard that checks unknown JSON before using it as a User.
Show solution
type User = { id: number; email: string };
function isUser(value: unknown): value is User {
return typeof value === 'object' && value !== null &&
typeof (value as User).id === 'number' && typeof (value as User).email === 'string';
}
for (const text of ['{"id":1,"email":"qa@test.com"}', '{"id":"1"}']) {
const data: unknown = JSON.parse(text);
console.log(isUser(data) ? `Valid user ${data.email}` : 'Invalid user data');
}
Output
Valid user qa@test.com
Invalid user data
Explanation: a type guard (value is User) checks at runtime and narrows the type at compile time.
26. Nullable fields from a formReal-world
Handle a form object where optional fields may be null.
Show solution
type Form = { name: string; company: string | null; phone?: string };
const forms: Form[] = [
{ name: 'Asha', company: 'Acme', phone: '98765' },
{ name: 'Ravi', company: null }
];
for (const f of forms) {
console.log(`${f.name} | ${f.company ?? 'Individual'} | ${f.phone ?? '-'}`);
}
Output
Asha | Acme | 98765
Ravi | Individual | -
Explanation: null (an explicit empty value) and optional fields are both handled with ??.
27. Record of expected textsReal-world
Store expected page titles in a Record keyed by route.
Show solution
type Route = '/' | '/login' | '/cart';
const expectedTitles: Record<Route, string> = { '/': 'Home', '/login': 'Sign in', '/cart': 'Your cart' };
const visited: Route[] = ['/login', '/cart'];
for (const route of visited) console.log(`${route} -> ${expectedTitles[route]}`);
Output
/login -> Sign in
/cart -> Your cart
Explanation: Record<Route, string> requires an entry for every route, so none can be forgotten.
28. Symbol as a unique keyReal-world
Use a symbol to attach hidden metadata to a test object.
Show solution
const meta: unique symbol = Symbol('meta');
const test = { title: 'Login', [meta]: { retries: 2 } };
console.log(Object.keys(test));
console.log(test[meta].retries);
Output
[ 'title' ]
2
Explanation: symbol keys do not appear in Object.keys, so metadata does not interfere with normal properties.
29. Date arithmetic with typesReal-world
Calculate a token expiry 30 minutes after a fixed issue time.
Show solution
const issuedAt: Date = new Date('2026-11-02T10:00:00Z');
const expiresAt: Date = new Date(issuedAt.getTime() + 30 * 60 * 1000);
const remainingMin: number = (expiresAt.getTime() - issuedAt.getTime()) / 60000;
console.log(expiresAt.toISOString(), remainingMin);
Output
2026-11-02T10:30:00.000Z 30
Explanation: getTime() returns a number of milliseconds, which makes date arithmetic type-safe.
30. Map with typed keys and valuesReal-world
Use a Map<string, number> to track response times per endpoint.
Show solution
const timings = new Map<string, number>();
timings.set('/api/login', 220);
timings.set('/api/cart', 480);
timings.set('/api/login', 180);
console.log(timings.size, timings.get('/api/login'));
const slow = [...timings].filter(([, ms]) => ms > 300).map(([url]) => url);
console.log('Slow:', slow.join(', '));
Output
2 180
Slow: /api/cart
Explanation: Map<K, V> types both keys and values. Setting an existing key replaces its value.
31. Set of unique tagsReal-world
Collect unique tags from several tests into a Set<string>.
Show solution
const testTags: string[][] = [['smoke', 'login'], ['regression', 'login'], ['smoke', 'cart']];
const unique = new Set<string>();
for (const tags of testTags) tags.forEach(t => unique.add(t));
console.log(unique.size, [...unique].sort().join(', '));
Output
4 cart, login, regression, smoke
Explanation: a typed Set stores each value once and only accepts the declared type.
1 thought on “TypeScript Data Types Practice Programs with Solutions (31 Programs)”