TypeScript Variables Practice Programs with Solutions (31 Programs)

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.

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 Variables lesson first.

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

Keep Practising

1 thought on “TypeScript Variables Practice Programs with Solutions (31 Programs)”

Leave a Comment