Practise TypeScript variables and 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 Variables lesson first.
- Beginner programs (1–10)
- Intermediate programs (11–20)
- Real-world QA scenario programs (21–31)
TypeScript Beginner Programs
1. Annotate basic variablesBeginner
Declare a string, a number and a boolean with explicit type annotations and print them.
Show solution
const testerName: string = 'Asha';
const experienceYears: number = 4;
const isLead: boolean = false;
console.log(testerName, experienceYears, isLead);
Output
Asha 4 false
Explanation: name: type tells TypeScript what a variable may hold, and the compiler checks every assignment against it.
2. Type inferenceBeginner
Declare variables without annotations and print their runtime types.
Show solution
let browser = 'chromium';
let retries = 2;
let headless = true;
console.log(typeof browser, typeof retries, typeof headless);
Output
string number boolean
Explanation: TypeScript infers string, number and boolean from the initial values, so annotations are often unnecessary.
3. Catch a wrong assignment at compile timeBeginner
Show that assigning a string to a number variable is a compile error, then print the runtime type.
Show solution
let count: number = 5;
// @ts-expect-error: Type 'string' is not assignable to type 'number'
count = 'five';
console.log(typeof count);
Output
string
Explanation: the compiler rejects the assignment. @ts-expect-error documents the expected error. Types are erased at runtime, which is why JavaScript still runs the line.
4. const narrows to a literal typeBeginner
Compare the inferred types of a const and a let holding 'qa'.
Show solution
const envConst = 'qa';
let envLet = 'qa';
envLet = 'staging';
const check: 'qa' = envConst;
console.log(check, envLet);
Output
qa staging
Explanation: a const string is inferred as the literal type 'qa', while a let widens to string because it can change.
5. Union type variableBeginner
Create a variable that may hold a number or a string ID.
Show solution
let userId: number | string = 101;
console.log(userId);
userId = 'USR-101';
console.log(userId);
Output
101
USR-101
Explanation: a union type A | B allows any of the listed types.
6. Literal union for allowed valuesBeginner
Restrict a browser variable to three allowed names.
Show solution
type BrowserName = 'chromium' | 'firefox' | 'webkit';
let browser: BrowserName = 'firefox';
console.log(browser);
// @ts-expect-error: '"edge"' is not assignable to type 'BrowserName'
browser = 'edge';
Output
firefox
Explanation: literal unions turn typos and unsupported values into compile errors.
7. Type aliasBeginner
Create a Url type alias and use it for two variables.
Show solution
type Url = string;
const baseUrl: Url = 'https://qa.shop.test';
const loginUrl: Url = `${baseUrl}/login`;
console.log(loginUrl);
Output
https://qa.shop.test/login
Explanation: a type alias gives a meaningful name to a type, which documents intent.
8. Typed array variableBeginner
Declare an array of test names with a type annotation and add an item.
Show solution
const tests: string[] = ['login', 'search'];
tests.push('checkout');
console.log(tests);
console.log(tests.length);
Output
[ 'login', 'search', 'checkout' ]
3
Explanation: string[] means “array of strings”, so only strings can be pushed.
9. Typed object variableBeginner
Declare a user variable with an inline object type.
Show solution
const user: { name: string; age: number; active: boolean } = { name: 'Ravi', age: 29, active: true };
console.log(`${user.name} (${user.age})`);
Output
Ravi (29)
Explanation: inline object types describe every property and its type.
10. Optional propertyBeginner
Declare an object type where phone is optional and create two users.
Show solution
type Contact = { email: string; phone?: string };
const a: Contact = { email: 'a@test.com' };
const b: Contact = { email: 'b@test.com', phone: '9876543210' };
console.log(a.phone ?? 'no phone', '|', b.phone ?? 'no phone');
Output
no phone | 9876543210
Explanation: ? marks a property as optional, so its type becomes string | undefined.
TypeScript Intermediate Programs
11. readonly propertiesIntermediate
Make id read-only and show the compile error when changing it.
Show solution
type TestCase = { readonly id: string; title: string };
const tc: TestCase = { id: 'TC-1', title: 'Login' };
tc.title = 'Login with SSO';
// @ts-expect-error: Cannot assign to 'id' because it is a read-only property
tc.id = 'TC-2';
console.log(tc.title);
Output
Login with SSO
Explanation: readonly prevents reassignment at compile time.
12. readonly arraysIntermediate
Declare a read-only list of supported browsers.
Show solution
const SUPPORTED: readonly string[] = ['chromium', 'firefox', 'webkit'];
console.log(SUPPORTED.includes('webkit'));
// @ts-expect-error: Property 'push' does not exist on type 'readonly string[]'
SUPPORTED.push('edge');
Output
true
Explanation: read-only arrays have no mutating methods in their type, which protects shared constants.
13. as constIntermediate
Freeze a config object’s types to literals with as const.
Show solution
const config = { browser: 'chromium', workers: 4 } as const;
const browser: 'chromium' = config.browser;
console.log(browser, config.workers);
Output
chromium 4
Explanation: as const makes properties readonly and keeps literal types instead of widening them to string or number.
14. Derive a union from an as const arrayIntermediate
Create a Env type from an array of environment names.
Show solution
const ENVS = ['dev', 'qa', 'staging'] as const;
type Env = (typeof ENVS)[number];
const current: Env = 'qa';
console.log(current, ENVS.length);
Output
qa 3
Explanation: (typeof ENVS)[number] turns the array’s literal values into a union type, so the list and the type stay in sync.
15. unknown requires narrowingIntermediate
Store a value of type unknown and narrow it before using it.
Show solution
let input: unknown = 'qa@example.com';
if (typeof input === 'string') {
console.log(input.toUpperCase());
}
input = 42;
if (typeof input === 'number') {
console.log(input.toFixed(1));
}
Output
QA@EXAMPLE.COM
42.0
Explanation: unknown accepts anything, but you must check its type before using it, unlike any.
16. any disables checkingIntermediate
Show that any allows unsafe operations that fail at runtime.
Show solution
let data: any = 'text';
try {
data.notAMethod();
} catch (error) {
console.log(error instanceof TypeError ? 'Runtime TypeError' : 'Other error');
}
Output
Runtime TypeError
Explanation: any turns off type checking, so mistakes surface only at runtime. Prefer unknown.
17. Definite assignmentIntermediate
Declare a variable that is assigned inside a branch and use it safely.
Show solution
let status: string;
const passed = 42 > 40;
if (passed) {
status = 'PASSED';
} else {
status = 'FAILED';
}
console.log(status);
Output
PASSED
Explanation: TypeScript tracks that status is assigned on every path, so reading it afterwards is allowed.
18. Template literal typesIntermediate
Create a TestId type that must look like TC-<number>.
Show solution
type TestId = `TC-${number}`;
const first: TestId = 'TC-101';
// @ts-expect-error: '"101"' is not assignable to type '`TC-${number}`'
const bad: TestId = '101';
console.log(first, bad);
Output
TC-101 101
Explanation: template literal types describe string patterns, which catches malformed IDs.
19. Destructuring with typesIntermediate
Destructure a typed credentials object into variables.
Show solution
type Credentials = { username: string; password: string; role?: 'admin' | 'viewer' };
const creds: Credentials = { username: 'qa_user', password: 'Secret123' };
const { username, role = 'viewer' } = creds;
console.log(username, role);
Output
qa_user viewer
Explanation: destructured variables keep their property types. The default value narrows role to the literal union.
20. Block-scoped typed variablesIntermediate
Use the same variable name with different types in two blocks.
Show solution
{
const result: number = 95;
console.log('score', result);
}
{
const result: string = 'passed';
console.log('status', result);
}
Output
score 95
status passed
Explanation: const and let are block-scoped, so each block can declare its own result with its own type.
TypeScript Real-World QA Scenario Programs
21. Environment URL mapReal-world
Use a Record to store base URLs for each environment and look one up.
Show solution
type Env = 'qa' | 'staging' | 'prod';
const BASE_URLS: Record<Env, string> = {
qa: 'https://qa.shop.test',
staging: 'https://staging.shop.test',
prod: 'https://shop.test'
};
const env: Env = 'staging';
console.log(`${BASE_URLS[env]}/login`);
Output
https://staging.shop.test/login
Explanation: Record<Env, string> forces a URL for every environment, so a missing one is a compile error.
22. Typed test configurationReal-world
Declare a typed config object with optional settings and defaults.
Show solution
type RunConfig = { baseURL: string; retries?: number; headless?: boolean };
const config: RunConfig = { baseURL: 'https://qa.app.test' };
const retries = config.retries ?? 0;
const headless = config.headless ?? true;
console.log(config.baseURL, retries, headless);
Output
https://qa.app.test 0 true
Explanation: optional properties plus ?? defaults is a typical shape for test settings.
23. Typed counters for resultsReal-world
Count test results with a typed counter object.
Show solution
type Status = 'passed' | 'failed' | 'skipped';
const counts: Record<Status, number> = { passed: 0, failed: 0, skipped: 0 };
const results: Status[] = ['passed', 'failed', 'passed', 'skipped', 'passed'];
for (const r of results) counts[r]++;
console.log(counts);
Output
{ passed: 3, failed: 1, skipped: 1 }
Explanation: indexing a Record<Status, number> with a Status value is fully type-safe.
24. Typed test userReal-world
Create a typed test user and build an email from it.
Show solution
type Role = 'admin' | 'editor' | 'viewer';
type TestUser = { id: number; name: string; role: Role };
const user: TestUser = { id: 7, name: 'meera', role: 'editor' };
const email = `${user.name}.${user.id}@example.com`;
console.log(email, user.role);
Output
meera.7@example.com editor
Explanation: a named type documents the test-data shape and catches invalid roles.
25. Read environment variables safelyReal-world
Read optional environment values with fallbacks and convert types.
Show solution
const env: Record<string, string | undefined> = { BASE_URL: 'https://qa.app.test', WORKERS: '4' };
const baseUrl: string = env.BASE_URL ?? 'http://localhost:3000';
const workers: number = Number(env.WORKERS ?? '2');
const debug: boolean = env.DEBUG === 'true';
console.log(baseUrl, workers, debug);
Output
https://qa.app.test 4 false
Explanation: environment values are string | undefined, so the types force you to handle missing values and convert them.
26. Timeouts as named constantsReal-world
Store timeouts in a typed constant object and calculate a total wait.
Show solution
const TIMEOUTS = { navigation: 30000, action: 10000, assertion: 5000 } as const;
type TimeoutName = keyof typeof TIMEOUTS;
const steps: TimeoutName[] = ['navigation', 'action', 'action', 'assertion'];
const worstCase = steps.reduce((sum, s) => sum + TIMEOUTS[s], 0);
console.log(`Worst case: ${worstCase / 1000}s`);
Output
Worst case: 55s
Explanation: keyof typeof builds a union of the object’s keys, so only real timeout names can be used.
27. Typed locator strategyReal-world
Model a locator as either a role or a test ID using a union of object types.
Show solution
type Locator = { by: 'role'; role: string; name: string } | { by: 'testId'; id: string };
const locators: Locator[] = [
{ by: 'role', role: 'button', name: 'Login' },
{ by: 'testId', id: 'cart-badge' }
];
for (const l of locators) {
console.log(l.by === 'role' ? `getByRole('${l.role}', { name: '${l.name}' })` : `getByTestId('${l.id}')`);
}
Output
getByRole('button', { name: 'Login' })
getByTestId('cart-badge')
Explanation: checking by narrows the union, so TypeScript knows which properties exist in each branch.
28. Track state with typed let variablesReal-world
Track login attempts with typed state variables.
Show solution
const MAX_ATTEMPTS: number = 3;
let attempts: number = 0;
let locked: boolean = false;
const outcomes: boolean[] = [false, false, false];
for (const ok of outcomes) {
if (ok) break;
attempts++;
locked = attempts >= MAX_ATTEMPTS;
}
console.log(`attempts=${attempts}, locked=${locked}`);
Output
attempts=3, locked=true
Explanation: explicit types make it clear which values change and what they may contain.
29. Build a typed report lineReal-world
Create typed variables for a test result and format a report line.
Show solution
type Result = { title: string; durationMs: number; status: 'passed' | 'failed' };
const result: Result = { title: 'Checkout with coupon', durationMs: 4210, status: 'passed' };
const line: string = `[${result.status.toUpperCase()}] ${result.title} - ${(result.durationMs / 1000).toFixed(1)}s`;
console.log(line);
Output
[PASSED] Checkout with coupon - 4.2s
Explanation: typed inputs guarantee that toUpperCase and toFixed are called on the right types.
30. Nullable variable for a found elementReal-world
Model a search result that may be null and handle both cases.
Show solution
const products = ['Mouse', 'Keyboard', 'Monitor'];
function findProduct(name: string): string | null {
return products.find(p => p === name) ?? null;
}
for (const name of ['Keyboard', 'Webcam']) {
const found: string | null = findProduct(name);
console.log(found === null ? `${name} not found` : `Found ${found}`);
}
Output
Found Keyboard
Webcam not found
Explanation: with strictNullChecks, a string | null value must be checked before it is used as a string.
31. Variables typed from another variableReal-world
Use typeof to type a new object like an existing default config.
Show solution
const defaultUser = { name: 'QA User', role: 'viewer', active: true };
const admin: typeof defaultUser = { ...defaultUser, name: 'Admin', role: 'admin' };
console.log(admin);
Output
{ name: 'Admin', role: 'admin', active: true }
Explanation: typeof value in a type position copies the inferred type of an existing value.
1 thought on “TypeScript Variables Practice Programs with Solutions (31 Programs)”