TypeScript Interview Questions for Production Judgment

On this page
- How to score TypeScript interview questions
- TypeScript interview questions on API boundaries
- 1. An API returns JSON for a customer. Where does its TypeScript type become trustworthy?
- 2. A hand-written type predicate checks only an id. Can it promise a complete customer?
- TypeScript interview questions on application states
- 3. A loading screen has data, error and loading flags. How would you prevent contradictory states?
- 4. A new cancelled state is added. How can the compiler expose forgotten handlers?
- Generics that preserve useful relationships
- 5. A property reader accepts any string key and returns any. What relationship should its signature preserve?
- 6. A generic fetch helper promises any caller-selected type. Is that a safe API?
- Nullability and assertions under pressure
- 7. A lookup uses a non-null assertion because the record usually exists. How would you review it?
- 8. A discount can be zero, absent or invalid. Which fallback preserves its meaning?
- Safe refactoring across existing callers
- 9. A legacy module accepts any. How would you improve its safety without rewriting every caller?
- 10. A customer name becomes nullable while old clients remain active. What makes the refactor safe?
- Run a short structured interview
TypeScript interview questions should reveal whether a developer can explain what the compiler knows, what incoming data still needs to prove, and how a contract change affects existing callers. Use these fictional production scenarios to assess mid-level and senior developers without turning the interview into JavaScript trivia or a React quiz.
TL;DR: Choose a consistent subset from these ten questions. Ask for the failure mechanism, a safe design and a concrete counterexample. Use the reference answers and follow-up probes to record evidence; treat the scoring anchors as editorial guidance that your team must calibrate.
How to score TypeScript interview questions
Agree on role requirements before choosing prompts. A mid-level developer might implement a safe boundary and explain its tests; a senior developer should also reason about compatibility and the cost of a shared abstraction. Do not require candidates to recite the reference answer word for word.
- Weak evidence: Repeats a syntax rule, adds an assertion or silences an error without explaining the risk.
- Adequate evidence: Identifies the failure, proposes a coherent correction and explains the relevant type relationship.
- Strong evidence: Tests a counterexample, discusses a realistic trade-off and follows the contract through consumers or deployment.
- Missing evidence: Record what you did not establish. A question you never asked is not a weak answer.
These anchors are not validated predictors of job performance. Compare candidates against the role criteria and the evidence you actually collected, using comparable prompts and follow-up time.
Tip: A technical interview preparation tool is useful for keeping prompts and reference answers together. Calibrate with interviewers first: ask each person to rate the same fictional response and explain any disagreement.
TypeScript interview questions on API boundaries
The TypeScript Handbook’s Everyday Types chapter explains that type assertions add no execution-time validation. Start here when a candidate treats an API response as trustworthy simply because it has a declared type.
1. An API returns JSON for a customer. Where does its TypeScript type become trustworthy?
Reference answer: At a boundary that checks the consumed contract. Receive an unknown value, validate the required fields and return either a usable domain value or a deliberate failure. Separate an unsuccessful HTTP response, malformed JSON and a valid JSON value with the wrong shape.
For this fictional screen, only a string identifier and display name are required:
function readCustomer(value: unknown): { id: string; name: string } { if ( typeof value !== 'object' || value === null || !('id' in value) || typeof value.id !== 'string' || !('name' in value) || typeof value.name !== 'string' ) { throw new Error('Invalid customer response'); } return { id: value.id, name: value.name }; }
- Follow-up probe: What should happen when the response is null or has a numeric name?
- Adequate signal: Checks both fields before use.
- Strong signal: Defines rejection behavior and distinguishes shape checks from business rules, such as whether an identifier may be empty.
2. A hand-written type predicate checks only an id. Can it promise a complete customer?
Reference answer: No. Match the promised shape to the checks, including required nested values. Either complete the validation or return a smaller type; choosing a validation library still requires the schema to match the consumed contract.
- Follow-up probe: How would you test an object with a valid id but a missing name?
- Adequate signal: Notices the unchecked property.
- Strong signal: Produces a counterexample and explains how to keep the checker aligned when the contract changes.
Key takeaway: Reward an explanation of where trust begins. A confident assertion is not evidence that the incoming value satisfies the application’s needs.
TypeScript interview questions on application states
The TypeScript Handbook’s Narrowing chapter covers discriminated unions and exhaustiveness checking. Apply them to a screen’s possible states, then ask which behaviors the type still cannot establish.
3. A loading screen has data, error and loading flags. How would you prevent contradictory states?
Reference answer: Give each valid case its required fields. This fictional starting model excludes success without data and failure without an error:
type CustomerState = | { status: 'idle' } | { status: 'loading' } | { status: 'success'; name: string } | { status: 'failure'; message: string };
- Follow-up probe: How would you represent a refresh that keeps the previous result visible?
- Adequate signal: Connects fields to cases instead of making everything optional.
- Strong signal: Asks about retained data and valid transitions. A state shape alone does not enforce the sequence of operations.
4. A new cancelled state is added. How can the compiler expose forgotten handlers?
Reference answer: Handle each declared case, then require the remaining value to be impossible. Adding another case makes that requirement fail until the handler changes.
function describeCustomer(state: CustomerState): string { switch (state.status) { case 'idle': return 'Choose a customer'; case 'loading': return 'Loading'; case 'success': return state.name; case 'failure': return state.message; } const unhandled: never = state; return unhandled; }
- Follow-up probe: Could a default branch returning an empty string conceal the missing case?
- Adequate signal: Adds an exhaustive check instead of a silent fallback.
- Strong signal: Demonstrates the compile error after extending the union, while keeping incoming-data validation separate.
Tip: Ask candidates to add a case during the conversation. Their response to the resulting error is more useful evidence than knowing the word “exhaustive.”
Generics that preserve useful relationships
The TypeScript Handbook’s Generics chapter explains inference and constraints. Ask candidates which relationship their abstraction preserves and whether that relationship makes ordinary callers easier to maintain.
5. A property reader accepts any string key and returns any. What relationship should its signature preserve?
Reference answer: Connect the key to the object’s known properties and retain the selected property’s type. Inference can then determine the result at a call site:
function readProperty<T, K extends keyof T>(object: T, key: K): T[K] { return object[key]; } const customerName = readProperty({ id: 7, name: 'Example' }, 'name');
- Follow-up probe: What is lost if the object is widened to a dictionary before the call?
- Adequate signal: Preserves the key/value relationship.
- Strong signal: Explains that optional properties can still produce undefined and that an arbitrary incoming string needs checking.
6. A generic fetch helper promises any caller-selected type. Is that a safe API?
Reference answer: A return type chosen by the caller does not inspect the response. Accept a decoder from unknown to the desired result, or return unknown and validate at the consumer. A concrete customer-loading helper may be clearer when there is only one real use case.
- Follow-up probe: Would adding a generic constraint validate the network response?
- Adequate signal: Requires a real decoder rather than hiding a cast.
- Strong signal: Weighs reuse against error handling, readable call sites and the work of maintaining a more general API.
Key takeaway: Sophisticated type syntax deserves credit only when it preserves a useful promise. Ask what callers gain and which checks still happen when the program executes.
Nullability and assertions under pressure
The TypeScript strictNullChecks reference explains that a lookup may return undefined and must be handled when this option is enabled. State that compiler setting explicitly before assessing an answer.
7. A lookup uses a non-null assertion because the record usually exists. How would you review it?
Reference answer: Decide what absence means. Return a domain outcome when a missing record is expected, or perform an explicit invariant check and fail deliberately when it is an internal error. The assertion itself adds no check.
- Follow-up probe: Does an empty string mean the same thing as a missing value here?
- Adequate signal: Handles the missing record.
- Strong signal: Chooses behavior from the domain contract and tests absence instead of inventing a misleading default.
8. A discount can be zero, absent or invalid. Which fallback preserves its meaning?
Reference answer: Define absence and invalidity separately. For this fictional input, a nullish fallback retains a legitimate zero; a truthiness fallback would replace it. Negative values and invalid numbers need a separate business rule.
function discountOrDefault(discount: number | null | undefined): number { return discount ?? 10; }
- Follow-up probe: What should happen for a negative discount or NaN?
- Adequate signal: Keeps zero distinct from absence.
- Strong signal: Explains why the fallback is not validation, then proposes concrete acceptance and rejection cases.
Tip: Supply a counterexample after the first answer. Ask the candidate to revise their reasoning aloud; do not score willingness to reconsider as a failure.
Safe refactoring across existing callers
Keep the discussion tied to an actual change sequence. These are editorial scenarios: the right rollout depends on who controls the producers, consumers and deployment order.
9. A legacy module accepts any. How would you improve its safety without rewriting every caller?
Reference answer: Identify the external boundary, validate unknown input there and pass a smaller domain type inward. Migrate one consumer path at a time with tests that preserve required behavior. Keep temporary escape hatches local, justified and removable.
- Follow-up probe: Which existing behavior would you characterize before the first change?
- Adequate signal: Migrates a concrete boundary and consumer.
- Strong signal: Covers invalid inputs, compatibility and removal of temporary assertions, rather than just renaming any to unknown everywhere.
10. A customer name becomes nullable while old clients remain active. What makes the refactor safe?
Reference answer: Define what null means, then inventory readers and writers, including exports and older clients. Make readers tolerant before new writers emit null when that deployment sequence is possible; otherwise consider a versioned contract. Exercise both shapes through the real integration boundary and plan rollback behavior.
- Follow-up probe: What if an old writer replaces a null with an empty string?
- Adequate signal: Changes consumers as well as the type declaration.
- Strong signal: Spots semantic drift during mixed-version operation and asks how compatibility will be demonstrated before enabling new writes.
Key takeaway: A senior answer should follow the change beyond the edited function. Ask who still sends the old shape and who will encounter the new one first.
Run a short structured interview
Use this illustrative 45-minute plan as a starting point. Select the same core questions for candidates assessed against the same role requirements; substitute deliberately when the role’s work differs.
- First five minutes: Explain the scenario, compiler assumptions and evaluation criteria; allow clarification.
- Next fifteen minutes: Use questions 1 and 3 to explore a boundary and an application-state model.
- Next fifteen minutes: Use question 5 for a mid-level role or question 10 for a senior role, with the corresponding follow-up probe.
- Final ten minutes: Let the candidate ask questions, then record evidence and unresolved gaps against the agreed anchors.

Use the sample as preparation material in your structured interview tool. It includes the ten prompts, follow-up probes and reference answers, with editorial scoring anchors in the answers. Your ratings and hiring decision still require interviewer judgment.

To load all 10 questions, open the Import section, choose TypeScript Production Judgment in the Sample Interview Data card and click Load. Use a workspace role with permission to publish questions, then review the material for your role before interviewing.
For adjacent coverage, use our JavaScript interview questions for language behavior and backend developer interview questions for service design. Use the question bank audit to decide which prompts to keep as the role changes.
Tip: Record the candidate’s concrete explanation beside your rating. “Caught the unchecked name and proposed a rejection test” gives the debrief more to work with than “good TypeScript knowledge.”