5 exercises — interface vs type alias, discriminated unions with exhaustive checking, built-in utility types, type narrowing, and reading TypeScript compiler errors in plain English.
0 / 22 completed
1 / 22
During a TypeScript code review, a reviewer comments: "Prefer an interface over a type alias here — this shape needs to be extendable." What is the key difference that makes the reviewer favour interface?
TypeScript interface vs. type alias — the practical distinction:
Use interface when: • You are describing the shape of an object or class • The type might need to be extended or merged later • Working with OOP patterns (class Foo implements IFoo) • Writing public API types for libraries (consumers can augment via declaration merging)
Use type alias when: • Describing a union: type Status = "pending" | "done" | "failed" • Creating a mapped type: type Partial<T> = { [K in keyof T]?: T[K] } • Creating a conditional type: type IsString<T> = T extends string ? true : false • Creating a tuple type: type Pair = [string, number]
Declaration merging (interface only): interface User { name: string; } interface User { age: number; } // TypeScript merges these — User now has both name and age
This is used extensively by: ambient module declarations, global type augmentation (e.g., extending Express Request), adding custom fields to third-party library types.
Vocabulary: • declaration merging — TypeScript automatically combines multiple declarations of the same identifier • extending an interface — interface Admin extends User { role: string; } • type alias — a named reference to any TypeScript type, including complex/utility types
2 / 22
A TypeScript PR description says: "Used a discriminated union with exhaustive never checking to make the switch statement future-proof." What does this mean?
Discriminated unions — the pattern explained:
A discriminated union is a union type where every member has a common literal property — the discriminant. TypeScript uses it to narrow types inside if / switch branches.
Example: type Shape = | { kind: "circle"; radius: number } | { kind: "square"; side: number } | { kind: "triangle"; base: number; height: number };
Here kind is the discriminant — a literal string type.
Exhaustive check with never: function area(s: Shape): number { switch (s.kind) { case "circle": return Math.PI * s.radius ** 2; case "square": return s.side ** 2; case "triangle": return 0.5 * s.base * s.height; default: const _exhaustive: never = s; // ← compile error if Shape grows return _exhaustive; } }
If someone adds | { kind: "pentagon"; ... } to the union but forgets to add a case, TypeScript errors: "Type '{ kind: "pentagon"; ... }' is not assignable to type 'never'." The compiler catches the missing case.
Vocabulary: • discriminant — the shared literal property that distinguishes union members • type narrowing — TypeScript inferring a more specific type inside a branch • exhaustive check — guaranteeing all cases are handled • never — the type with no possible values; used as the "impossible case" in exhaustive checks • future-proof — the code will produce a compile error, not a silent bug, when extended
3 / 22
A developer reads a utility type usage: type UpdateUser = Partial<Pick<User, "name" | "email" | "bio">>. How would you explain this in plain English to a junior engineer?
TypeScript's built-in utility types are composable transformations on types — each one takes a type and returns a modified type.
Core utility types:
Partial<T> — makes all properties optional (name?: string instead of name: string). Use for PATCH endpoints where any subset of fields may be updated.
Required<T> — inverse: makes all optional properties required.
Pick<T, K> — creates a type with only the specified keys from T. Pick<User, "name" | "email"> gives { name: string; email: string }.
Omit<T, K> — inverse of Pick: creates a type with all keys except the specified ones. Omit<User, "password" | "role"> gives a safe "public user" shape.
Readonly<T> — all properties become readonly.
Record<K, V> — creates an object type with keys K and values V. Record<string, number> = { [key: string]: number }.
ReturnType<T> — extracts the return type of a function type.
Parameters<T> — extracts the parameter types as a tuple.
Composing utility types: Partial<Pick<User, "name" | "email" | "bio">> reads right-to-left: 1. Pick<User, ...> — take only name, email, and bio from User 2. Partial<...> — make all of them optional
This is the TypeScript idiom for defining a "partial update" DTO (Data Transfer Object).
4 / 22
A TypeScript function signature: function processInput(val: string | number | null) { if (typeof val === "string") { val.toUpperCase(); } }. What is happening inside the if block?
Type narrowing is TypeScript's ability to refine a broad union type to a specific type within a conditional block — automatically, without a cast.
Type narrowing techniques:
1. typeof narrowing — works for primitives: typeof val === "string" → narrows to string typeof val === "number" → narrows to number typeof val === "function" → narrows to Function
2. instanceof narrowing — works for objects: if (error instanceof NetworkError) { error.statusCode }
3. in operator narrowing — checks if property exists: if ("radius" in shape) { shape.radius } → narrows to shapes with radius
5. User-defined type guard: function isString(val: unknown): val is string { return typeof val === "string"; } The val is string return type tells TypeScript: "if this function returns true, narrow the argument to string."
Type narrowing vocabulary: • narrow — reduce the set of possible types inside a branch • type guard — an expression that narrows a type; can be built-in (typeof) or user-defined • control flow analysis — TypeScript's tracking of types through branches and loops • type predicate (x is T) — the return type of a user-defined type guard function
5 / 22
A developer shares a tsc error: Type '{ name: string; age: string; }' is not assignable to type 'User'. Types of property 'age' are incompatible. Type 'string' is not assignable to type 'number'. How would you explain this error to someone new to TypeScript?
Reading TypeScript error messages — a structured approach:
The error format: Type 'A' is not assignable to type 'B' — you tried to assign something of type A where type B is expected. A is what you provided; B is what is required.
Reading the error in this exercise: 1. "Type '{ name: string; age: string; }' is not assignable to type 'User'" — you have an object literal, and TypeScript is trying to use it as a User 2. "Types of property 'age' are incompatible" — the problem is specifically the age field 3. "Type 'string' is not assignable to type 'number'" — age was given as a string ("25") but User declares it as a number (25)
Common TypeScript errors and their plain-English meaning: • "is not assignable to type" — type mismatch at assignment point • "Property X does not exist on type Y" — typo in property name, or the type doesn't have that field • "Object is possibly null" — you need to add a null check before accessing the value • "Argument of type X is not assignable to parameter of type Y" — wrong type passed to a function • "Cannot find name X" — variable/function not in scope, or missing import • "Index signature" errors — accessing an object with a dynamic key when TypeScript can't verify the key exists
Debugging mental model: TypeScript errors always point to where the type system detected a conflict. Read from the bottom of the error stack up — the innermost message is usually the actual type mismatch; outer messages are "container" context.
6 / 22
Reviewer: 'I'm seeing a lot of `keyof` usage here. While it's powerful, consider if we could leverage mapped types for improved readability and maintainability, especially as the object shapes evolve. It might simplify future type manipulations.' What is the reviewer primarily suggesting?
The reviewer is advocating for the use of mapped types as a more readable and maintainable alternative to keyof. While keyof offers powerful type manipulation capabilities, mapped types can often provide a clearer representation of intent, particularly when dealing with complex object shapes and potential future changes. Using mapped types reduces cognitive load and simplifies future refactoring.
7 / 22
Slack Message from Sarah: 'Just used a conditional type to handle different API responses. It's super clean and avoids the need for a ton of `if` statements. Using it with a discriminated union makes the whole thing really robust.' What technique is Sarah describing?
Sarah is describing the use of a *conditional type* within a *discriminated union*. Conditional types allow you to create new types based on runtime conditions. Combining this with a discriminated union provides a robust way to handle different response shapes from an API, ensuring that TypeScript correctly infers and enforces the expected types for each case.
8 / 22
PR Description: 'Implemented a utility type to safely update user profiles. It uses Pick and Partial to allow only specific fields to be modified, preventing accidental updates to sensitive data.' What is the primary benefit of this approach?
The use of Pick and Partial in this utility type creates a mechanism to restrict which fields can be modified during an update operation. This significantly reduces the risk of accidentally changing or deleting crucial data, such as sensitive information like passwords or unique identifiers – a core principle of safe coding practices.
9 / 22
Team Lead (during standup): 'We're using a `GenerateIndexType` to automatically generate index signatures for our object types. It saves us a lot of time and ensures type safety.' What is the primary purpose of GenerateIndexType?
GenerateIndexType is a utility type in TypeScript that creates an interface defining all possible keys within a given object. This ensures that when accessing properties using those keys, TypeScript can perform static type checking, preventing runtime errors caused by attempting to access non-existent properties. This is crucial for maintaining type safety and catching potential bugs during development.
10 / 22
A developer is writing a TypeScript function to process data from an API. The API response sometimes includes a `userId` property, but other times it doesn't. Which type definition would best handle this scenario while maintaining strong typing?
interface User {
id: number |
userId?: string;
}
The correct option uses the union type `number | undefined` for `id` to allow both a number and the absence of an ID. The `userId?` makes it optional, correctly reflecting the API response's potential lack of this property. Options A and B are too rigid, while option C incorrectly assumes `id` always exists.
11 / 22
During a code review, a senior developer points out that you're using a type alias to represent a complex shape. They suggest using an interface instead. What is the primary reason for this recommendation?
interface User {
name: string;
age: number;
}
type User = { name: string; age: number; };
The key difference is extensibility. Interfaces are designed to be extended using `extends`, allowing you to create specialized versions of the same shape. Type aliases are essentially just renamed types and cannot inherit from other types. While interfaces *can* be more concise, extensibility is the core reason for this recommendation.
12 / 22
A developer is using a discriminated union to handle different API responses. The response can either be a `User` object or an `Error` object. Which TypeScript feature would best ensure that all possible cases are handled and prevent unexpected runtime errors?
interface User { type: 'user'; data: { name: string; }; }
type Error { type: 'error'; message: string; }
discriminated union...
A discriminated union relies on a common property (the `type` field in this example) to distinguish between different variants. The `switch` statement then uses this property for explicit type checking at runtime, preventing the program from attempting operations that are not valid for the specific response type. Options B and C require more verbose code, while option D is insufficient without explicit type checks.
13 / 22
A TypeScript function receives an input value as a string or number. Inside the function, you need to convert it to a number if it's a string. Which type definition is most appropriate for the `val` parameter?
function processInput(val: string | number) { ... }
The correct option uses a union type `string | number`. This allows the function to accept either a string or a number. Option B is incorrect because it attempts to cast the type, which can lead to unexpected behavior. Options C and D are invalid union types.
14 / 22
During a TypeScript code review, a reviewer comments: 'This type is overly restrictive. Consider using a mapped type to allow for more flexible property additions without breaking existing code.' The code currently uses `type User = { name: string; age?: number; }`. What does the reviewer likely mean in this context?
The reviewer is highlighting the potential drawbacks of rigidly defining all properties in a type. Mapped types offer a way to extend or modify a type's shape without causing compatibility issues with older code that might rely on the original definition. The use of `age?: number` allows for optional values, which can be more flexible but also introduces complexity if not handled carefully. This is a common concern when dealing with evolving API schemas.
15 / 22
A Slack message from David reads: 'I'm using a conditional type to handle different response shapes from the backend. It's a bit complex, but it avoids having a massive `if` statement chain. I'm leveraging a discriminated union and nested types to manage the various scenarios.' What is David primarily trying to achieve with this approach?
David is utilizing conditional types to dynamically determine which type definition to apply based on the shape of the incoming data. This avoids complex nested `if` statements that become difficult to maintain and debug. A discriminated union combined with nested types provides a structured way to represent these different scenarios within TypeScript.
16 / 22
A developer is writing a TypeScript function that receives an array of strings and converts each string to a number. The function uses the following type definition: `function parseStrings(strings: (string | number)[]): number[]`. What does the union type (`string | number`) in this context allow?
The union type `(string | number)` indicates that the `strings` parameter can accept either a string or a number. The TypeScript compiler will then attempt to convert each element of the array to a number if it's a string. This offers flexibility but also requires careful handling of potential conversion errors, which isn't explicitly addressed in this simple example.
17 / 22
A developer is working with a complex TypeScript project. They've noticed that many type definitions are becoming increasingly large and difficult to understand. What technique could they use to improve the readability and maintainability of these types?
The best approach is to break down large, monolithic type definitions into smaller, more manageable units. This improves readability and allows for better modularity. Type aliases can be used to encapsulate specific parts of a complex shape, while interfaces help enforce the structure at the object level. Generic types with multiple constraints are particularly useful for reducing code duplication when dealing with similar shapes.
18 / 22
During a code review, a senior developer suggests replacing a large, complex type alias with an interface. What is the MOST significant reason for this recommendation?
The primary reason for recommending an interface over a complex type alias is the superior tooling support and refactoring capabilities provided by interfaces. Interfaces are designed for structural typing, making changes easier to manage and reducing the risk of introducing errors during modifications – this is where the most common misconception lies.
19 / 22
A developer needs to define a type that accepts either an array of strings or an array of numbers. Which type definition would be MOST appropriate?
The `|` (union) operator in TypeScript is designed precisely for this scenario – to allow a variable to hold values of different types. Option 1 directly represents an array that can be either strings or numbers. Options 2 and 3 are less precise and don't fully capture the type requirements.
20 / 22
A TypeScript function receives a `userId` that might be null or undefined. Which type definition would best handle this scenario while ensuring correct type checking?
To accurately represent the possibility of `null` or `undefined`, you must include all these variations in the type definition. Option 3 (`number | null | undefined`) explicitly allows for all three scenarios, providing robust type safety and preventing potential runtime errors when attempting to access properties on a potentially null/undefined value.
21 / 22
Reviewer Mark comments: 'I'm seeing frequent use of `keyof` to check object shapes. While effective for runtime validation, it can lead to verbose type definitions and potential performance bottlenecks. Have you considered using a more expressive approach like a mapped type with a union of possible keys?'. What is the primary benefit Mark highlights when suggesting a mapped type instead of `keyof`?
Mark is focusing on efficiency and readability. `keyof` can be verbose and, depending on the complexity of the object shapes being checked, might introduce performance overhead. Mapped types offer a more concise and potentially faster way to represent these checks, reducing code clutter and potential runtime costs. The other options address different aspects of type checking or index signatures, not the core concern Mark raises.
22 / 22
Slack Message from Elena: 'Just implemented a conditional type to handle variations in API response schemas. It's using a discriminated union and is significantly cleaner than a series of `if` statements. I'm extending the discriminated union with new cases as the backend evolves. Can anyone suggest a way to ensure we don't accidentally introduce breaking changes when adding these new cases?'
Elena's situation highlights the need for careful management of evolving schemas. Type guards provide precise control over case selection within the discriminated union. While other options might be useful in different contexts, a type guard is the most direct approach to handling variations and preventing unintended consequences when adding new cases while maintaining type safety.
What does the "TypeScript — Advanced Types" vocabulary exercise cover?
This exercise tests real IT vocabulary related to typescript — advanced types through 22 multiple-choice questions, each built from realistic workplace sentences rather than abstract definitions.
Is this vocabulary exercise free to use?
Yes. Every exercise on CoderSlingo, including this one, is completely free — no account, sign-up, or payment required.
How many questions does this exercise have?
This exercise has 22 questions. Each one shows a real-world sentence or scenario with multiple-choice options and an explanation once you answer.
What happens after I answer a question?
You'll see immediate feedback showing whether your answer was correct, along with a short explanation of why — then a button to move to the next question, and a full results screen at the end.
Can I retry the exercise if I get questions wrong?
Yes. Once you reach the results screen, click "Try again" to reset your answers and go through the exercise from the start as many times as you like.
Do I need to create an account to take this exercise?
No account is needed. Your answers are scored in your browser during the session — nothing is saved to a server, so you can jump straight in.
Is my progress saved if I leave the page?
No — progress within an exercise resets if you navigate away or reload. Each exercise is short enough to complete in a few minutes in one sitting.
Are these vocabulary exercises connected to other topics?
Yes — browse the full vocabulary exercises hub to find related modules covering adjacent IT topics and roles.
How is this different from reading a glossary or blog article?
Exercises like this one are active recall drills — you have to choose the correct term or phrasing yourself, which builds retention faster than passively reading a definition.
Where can I find more vocabulary exercises?
Browse the full Vocabulary exercises hub for hundreds of modules covering Agile, DevOps, security, databases, architecture, and more — organised by IT role and skill.