7 Tricky TypeScript Interview Questions (ANSWERED)

·12 min read
TypeScriptInterview QuestionsTricky QuestionsGenericsConditional TypesType SystemFrontendAdvanced TypeScript

TypeScript adoption has grown to 78% among JavaScript developers, yet the language's most powerful features—never, infer, conditional types—remain a mystery to most. Here's a telling statistic: in technical screens, candidates can typically explain what interface and type do, but fewer than 30% can explain when the never type is actually useful.

These seven questions test concepts that look simple on the surface but require genuine understanding to explain well. They reveal whether you've truly internalized TypeScript's type system or just memorized syntax patterns.

Question 1: The any vs unknown Trap

This question appears early in many TypeScript interviews, but the depth of your answer reveals a lot:

function processValue(value: any): string {
    return value.toUpperCase();
}
 
function processValueSafely(value: unknown): string {
    return value.toUpperCase(); // Error!
}

Why the second function fails:

Both any and unknown can accept any value. The difference is what you can do afterward. With any, TypeScript throws up its hands and says "do whatever you want—I'm not checking." With unknown, TypeScript says "I don't know what this is, so prove it before you use it."

The safe version requires narrowing:

function processValueSafely(value: unknown): string {
    if (typeof value === 'string') {
        return value.toUpperCase(); // Now TypeScript knows it's a string
    }
    throw new Error('Expected a string');
}

Think of any as turning off the alarm system. Think of unknown as saying "something entered the building—verify their identity before letting them into secure areas."

What interviewers are looking for: They want to hear that unknown is the type-safe counterpart to any. The best candidates explain that any essentially disables type checking while unknown preserves it by requiring narrowing. Bonus points if you mention that unknown was added in TypeScript 3.0 specifically to address the type safety issues with any.

Common follow-up: "When would you actually use any?" A good answer acknowledges that any has legitimate uses—migrating JavaScript codebases, dealing with truly dynamic data where exhaustive type guards aren't practical, or temporarily bypassing type errors during prototyping. But the key is treating it as a last resort, not a default.

Question 2: The never Type Exhaustiveness Check

This question separates developers who've memorized the definition of never from those who understand its practical applications:

type Shape =
    | { kind: 'circle'; radius: number }
    | { kind: 'square'; side: number }
    | { kind: 'triangle'; base: number; height: number };
 
function getArea(shape: Shape): number {
    switch (shape.kind) {
        case 'circle':
            return Math.PI * shape.radius ** 2;
        case 'square':
            return shape.side ** 2;
        default:
            const exhaustiveCheck: never = shape;
            throw new Error(`Unhandled shape: ${exhaustiveCheck}`);
    }
}

What happens here:

This code has a bug—we forgot to handle the 'triangle' case. But here's the clever part: TypeScript catches it at compile time because of that never assignment.

After the 'circle' and 'square' cases, the only remaining possibility for shape is the triangle variant. When we try to assign this non-never value to a variable of type never, TypeScript errors: "Type triangle is not assignable to type 'never'."

The never type represents the impossible—a value that can never exist. If our switch was truly exhaustive, shape would have no possible values left in the default case, and assigning "nothing" to never is fine. But if we missed a case, there's still a possible value, and TypeScript complains.

What interviewers are looking for: Understanding that never is the "bottom type"—nothing can be assigned to it except another never. Candidates who explain the exhaustiveness pattern and why it catches bugs at compile time demonstrate real TypeScript fluency. The best answers also mention that this pattern is particularly valuable when unions grow over time—adding a new shape variant to the union will immediately surface any switch statements that need updating.

Question 3: The infer Keyword Puzzle

This question tests whether you've worked with advanced conditional types:

type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type Parameters<T> = T extends (...args: infer P) => any ? P : never;
 
function greet(name: string, age: number): string {
    return `Hello ${name}, you are ${age}`;
}
 
type GreetReturn = ReturnType<typeof greet>;     // string
type GreetParams = Parameters<typeof greet>;     // [string, number]

How infer works:

The infer keyword lets you "capture" a type from within a conditional type. It's like a type-level variable that gets its value from pattern matching.

In T extends (...args: any[]) => infer R ? R : never:

  • We check if T matches the function pattern
  • If it does, R captures whatever the return type is
  • We then return that captured R

Think of it like destructuring, but for types. Just as const { name } = user extracts the name property from an object, infer R extracts a type from within another type's structure.

Here's a more complex example that extracts the element type from a Promise:

type Awaited<T> = T extends Promise<infer U> ? U : T;
 
type A = Awaited<Promise<string>>;        // string
type B = Awaited<Promise<Promise<number>>; // Promise<number>
 
// Recursive version for nested promises
type DeepAwaited<T> = T extends Promise<infer U> ? DeepAwaited<U> : T;
type C = DeepAwaited<Promise<Promise<number>>>; // number

What interviewers are looking for: The ability to explain infer without getting lost in abstract descriptions. Candidates who use analogies (pattern matching, destructuring) and show practical examples demonstrate genuine understanding. The best answers mention that infer can only appear in the "extends" clause of a conditional type and that it introduces a type variable that's only in scope within the "true" branch.

Question 4: The Mapped Type Modifier Mystery

This question tests understanding of mapped types and their modifiers:

interface User {
    readonly id: number;
    name: string;
    email?: string;
}
 
type Mutable<T> = {
    -readonly [K in keyof T]: T[K];
};
 
type Required<T> = {
    [K in keyof T]-?: T[K];
};
 
type MutableUser = Mutable<User>;
// { id: number; name: string; email?: string }
 
type RequiredUser = Required<User>;
// { readonly id: number; name: string; email: string }

What those minus signs do:

In mapped types, you can add or remove modifiers using + and -. The + is implied when you write readonly or ?, but the - explicitly removes them.

So -readonly strips the readonly modifier from all properties, and -? makes optional properties required. This is how TypeScript's built-in Required<T> utility type works.

You can combine them too:

type Writeable<T> = {
    -readonly [K in keyof T]-?: T[K];
};
 
// Makes everything mutable AND required
type FullUser = Writeable<User>;
// { id: number; name: string; email: string }

What interviewers are looking for: Knowledge that mapped types can modify property characteristics, not just transform types. Candidates who can explain the modifier syntax (+readonly, -readonly, +?, -?) and demonstrate practical applications show advanced TypeScript skills.

Question 5: The Generic Constraint Gotcha

This question reveals whether you understand how generic constraints interact with type inference:

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
    return obj[key];
}
 
const user = { name: 'Alice', age: 30, active: true };
 
const name = getProperty(user, 'name');     // string
const age = getProperty(user, 'age');       // number
const active = getProperty(user, 'active'); // boolean
const invalid = getProperty(user, 'email'); // Error!

Why this pattern is powerful:

The constraint K extends keyof T ensures that K can only be a valid key of T. But the return type T[K] is where the magic happens—it uses indexed access types to return the specific type of that property.

Without the generic K, you'd have to return a union of all possible property types:

function getPropertyLoose<T>(obj: T, key: keyof T): T[keyof T] {
    return obj[key];
}
 
const nameLoose = getPropertyLoose(user, 'name'); // string | number | boolean

The generic version preserves the precise type because TypeScript tracks which specific key was passed.

Here's where it gets tricky—understanding when constraints are checked:

function merge<T extends object, U extends object>(a: T, b: U): T & U {
    return { ...a, ...b };
}
 
// Works fine
const result = merge({ name: 'Alice' }, { age: 30 });
 
// Also works - constraints are structural, not nominal
const withArray = merge({ items: [1, 2, 3] }, { count: 3 });

What interviewers are looking for: Understanding that constraints restrict what can be passed but don't affect inference within those bounds. The best candidates explain indexed access types (T[K]) and why preserving the specific key type matters for accurate return type inference.

Question 6: The Conditional Distribution Surprise

This question catches many experienced developers off guard:

type ToArray<T> = T extends any ? T[] : never;
 
type Result1 = ToArray<string>;           // string[]
type Result2 = ToArray<string | number>;  // string[] | number[]
 
// Wait, why isn't Result2 (string | number)[]?

What's happening:

Conditional types distribute over union types when the checked type is a naked type parameter. This means ToArray<string | number> isn't evaluated as one check. Instead, TypeScript applies the conditional to each union member separately:

  1. ToArray<string>string[]
  2. ToArray<number>number[]
  3. Combine: string[] | number[]

To prevent distribution and get (string | number)[], wrap the type parameter in a tuple:

type ToArrayNonDistributive<T> = [T] extends [any] ? T[] : never;
 
type Result3 = ToArrayNonDistributive<string | number>; // (string | number)[]

The brackets don't change the condition—[T] extends [any] is still always true. But wrapping in a tuple prevents the distribution behavior.

What interviewers are looking for: Awareness of distributive conditional types and the bracket trick to disable distribution. This is genuinely advanced TypeScript knowledge. Candidates who can explain why distribution happens (type parameter must be "naked" in the extends clause) and when it's useful versus problematic demonstrate expert-level understanding.

Question 7: The Readonly Array vs Tuple Distinction

This subtle question tests understanding of TypeScript's array and tuple types:

const arr1 = [1, 2, 3];           // number[]
const arr2 = [1, 2, 3] as const;  // readonly [1, 2, 3]
 
function sum(numbers: number[]): number {
    return numbers.reduce((a, b) => a + b, 0);
}
 
sum(arr1); // Works
sum(arr2); // Error: readonly [1, 2, 3] not assignable to number[]

Why the error occurs:

as const does two things: it makes the array readonly and converts it to a tuple type with literal values. The issue is that number[] implies the array could be mutated (you could push, pop, etc.), but readonly [1, 2, 3] guarantees immutability.

TypeScript won't let you pass a readonly array where a mutable one is expected because the function could theoretically modify it. The fix is to declare the parameter as readonly:

function sum(numbers: readonly number[]): number {
    return numbers.reduce((a, b) => a + b, 0);
}
 
sum(arr1); // Works - mutable arrays are assignable to readonly
sum(arr2); // Works now

Notice that a mutable array can be passed where a readonly one is expected (widening is safe), but not vice versa.

What interviewers are looking for: Understanding the difference between array mutability (readonly) and array length/type specificity (tuples). The best candidates explain that as const creates the most specific type possible—both immutable and tuple-typed—and that readonly parameters are more flexible for function signatures.

Patterns That Will Save You in TypeScript Interviews

After walking through these questions, here are the key concepts that separate TypeScript experts from intermediate users:

Understand the type hierarchy. never is at the bottom (assignable to everything, nothing assignable to it except never), unknown is at the top (everything assignable to it, nothing assignable from it except any/unknown). Everything else sits in between based on structural compatibility.

Think about variance. Function parameters are contravariant, return types are covariant. This is why readonly arrays aren't assignable to mutable array parameters—it's about what operations the type permits, not what values it holds.

Know when distribution applies. Conditional types distribute over unions when the type parameter appears "naked" in the extends clause. Wrapping in brackets disables this.

Use never strategically. Beyond exhaustiveness checks, never is useful in conditional types for filtering: type NonFunction<T> = T extends Function ? never : T removes function types from unions.

Practice Challenge

Here's a question to test your understanding. What type does Result resolve to?

type ExtractArrayType<T> = T extends (infer U)[] ? U : never;
 
type Result = ExtractArrayType<string[] | number[] | boolean>;

Think through it step by step. The conditional distributes over the union. string[] matches the pattern, so we get string. number[] matches, giving number. boolean doesn't match (infer U)[], so it becomes never. Combining: string | number | never, which simplifies to string | number (since never disappears in unions).

If you got that right, you're ready for advanced TypeScript interviews.

Wrapping Up

These seven questions represent the concepts that consistently distinguish TypeScript experts from intermediate developers. They're not trick questions designed to make you fail—they're diagnostic tools that reveal whether you've moved beyond surface-level usage to truly understanding the type system.

The common thread? Each question tests whether you can reason about types as a system, not just memorize syntax. Interviewers want to see that you understand why TypeScript makes certain choices, not just what those choices are.

Want to practice more? Our collection includes 800+ interview questions covering TypeScript, JavaScript, React, Angular, and more—each with detailed explanations like these.


Related Articles

If you found this helpful, check out these related guides:

Ready to ace your interview?

Get 550+ interview questions with detailed answers in our comprehensive PDF guides.

View PDF Guides