290: JavaScript and TypeScript Interview Review
Learning outcomes
By the end of this lesson, you can:
- explain and apply JavaScript execution in a realistic implementation;
- explain and apply async reasoning in a realistic implementation;
- explain and apply TypeScript boundaries in a realistic implementation;
- explain and apply advanced typing in a realistic implementation;
- explain and apply modules in a realistic implementation.
Prerequisites and retrieval
This review assumes the earlier 01–06 foundation and the preceding lessons in this module. Before you start, retrieve one concrete example from a previous project where one of these concerns showed up. It might be a closure that retained state, a race between asynchronous operations, an unsafe API response, or a module-resolution problem. The point is not to recite terminology. The point is to make a defensible decision in a realistic full-stack interview loop, where your explanation, trade-offs, debugging process, code, and project evidence need to tell the same story.
Terminology
- JavaScript execution: Review scope and closures,
this, prototypes and classes, the event loop and microtasks, promises andasync, modules, coercion, errors, iterators, and memory/reference semantics from the earlier lessons. - Async reasoning: Be able to put synchronous code, promise microtasks, timers, and
asynccontinuations in the right order and explain why. Memorized output puzzles are not enough; use the event-loop model to reason about behavior. - TypeScript boundaries: Explain type erasure,
unknownversusany, runtime validation, structural typing, optional versusnulldistinctions, assertions, and strict compiler options. - Advanced typing: Practice discriminated unions, generics and constraints,
keyofand indexed access, mapped and conditional types, utility types, and overload-versus-generic API design. - Modules: Explain ESM/CommonJS differences, NodeNext and bundler resolution, type-only imports, package exports, and why
tsconfigpath aliases can fail at runtime. - Practical code review: Given a TypeScript function, identify unsafe assertions, swallowed errors, mutable shared state, incorrect generic relationships, and boundary values that require runtime parsing.
Mental model
Approach JavaScript and TypeScript Interview Review as a design problem. Start with observable inputs and outputs, then make the invariants and failure modes explicit. Language interviews test runtime semantics and type-system reasoning together, so distinguish what JavaScript will actually do at runtime from what TypeScript can check statically. A strong implementation narrows uncertainty at the boundary and leaves evidence—tests, types, constraints, metrics, or diagrams—that supports the safety of the design.
A useful sequence for both an interview answer and production work is:
requirement -> constraints -> model -> implementation -> failure analysis -> verification
Do not go straight from a requirement to a library call. First say what must remain true. Then choose the mechanism that enforces that property, and explain how you would verify it when the normal path is no longer enough.
Deep dive
1. JavaScript execution
Review scope and closures, this, prototypes and classes, the event loop and microtasks, promises and async, modules, coercion, errors, iterators, and memory/reference semantics from the earlier lessons. These topics are connected by one practical question: what values and behavior exist at runtime, and for how long?
Decision rule: Use JavaScript execution deliberately when it makes the contract or invariant easier to prove. If a technique merely reduces typing while hiding an assumption, prefer the more explicit design. This is especially useful when explaining a closure, a reference shared between callers, or an asynchronous callback: describe the runtime behavior rather than relying on a label alone.
2. Async reasoning
Be able to order synchronous code, promise microtasks, timers, and asynchronous continuations and explain the ordering. Do not rely on memorized output puzzles without the event-loop model. In real code, the same reasoning tells you whether a state update can race with another update, whether an error is observed by the caller, and whether work continues after a request should have been cancelled.
Decision rule: Use async reasoning deliberately when it makes the contract or invariant easier to prove. If it only reduces typing while hiding an assumption, prefer the more explicit design. State what can run concurrently, how failure propagates, and what ordering the caller is actually promised.
3. TypeScript boundaries
Explain type erasure, unknown versus any, runtime validation, structural typing, optional versus null distinctions, assertions, and strict compiler options. The useful boundary is between checked source code and values arriving from outside it: TypeScript does not validate an HTTP response, database row, environment variable, or persisted document at runtime. An assertion can silence the compiler, but it cannot make an invalid value safe.
Decision rule: Use TypeScript boundaries deliberately when they make the contract or invariant easier to prove. If they only reduce typing while hiding an assumption, prefer the more explicit design. Keep external data unknown until a runtime parser or validator establishes the shape your domain code expects.
4. Advanced typing
Practice discriminated unions, generics and constraints, keyof and indexed access, mapped and conditional types, utility types, and overload-versus-generic API design. These features are valuable when they preserve a relationship between inputs and outputs, not when they are added merely to make a signature look sophisticated. In an interview, be ready to explain what relationship the type encodes and what invalid call it prevents.
Decision rule: Use advanced typing deliberately when it makes the contract or invariant easier to prove. If it only reduces typing while hiding an assumption, prefer the more explicit design. A simpler type that is easy for the team to read is often safer than a clever type that nobody can maintain.
5. Modules
Explain ESM/CommonJS differences, NodeNext and bundler resolution, type-only imports, package exports, and why tsconfig path aliases may fail at runtime. A source import is not the whole story: the compiler, bundler, and runtime each resolve modules according to their own configuration and rules. A path alias that TypeScript understands may still produce a runtime failure if the emitted code or deployed runtime does not understand that alias.
Decision rule: Use modules deliberately when they make the contract or invariant easier to prove. If they only reduce typing while hiding an assumption, prefer the more explicit design. When debugging an import, inspect the emitted module format, package metadata, resolver configuration, and the runtime that actually executes the code.
6. Practical code review
Given a TypeScript function, identify unsafe assertions, swallowed errors, mutable shared state, incorrect generic relationships, and boundary values that require runtime parsing. Do not stop at pointing out a suspicious line. Explain which invariant the line breaks, which layer owns the fix, and what test or observation would distinguish the failure from nearby possibilities.
Decision rule: Use practical code review deliberately when it makes the contract or invariant easier to prove. If it only reduces typing while hiding an assumption, prefer the more explicit design. A useful review connects the implementation to its caller, its dependencies, and the behavior that users or operators will observe.
Worked example
Consider a realistic full-stack interview loop in which the explanation, trade-offs, debugging, implementation, and project evidence all need to agree. Begin by writing the requirement in one sentence. List the input and output contracts, then identify which concept owns each likely failure mode. The key design move is separation: parsing and validation belong at the boundary; domain rules belong in the domain or service layer; persistence rules belong in the database or repository; presentation rules belong in the client. Mixing these concerns can make a happy-path demo look shorter, but it makes edge cases and failures much harder to reason about.
Prompt -> clarify -> state assumptions -> solve -> test edge cases -> explain trade-offs
Walk through at least four cases:
- the normal path;
- an empty or missing value;
- a duplicate, retry, or concurrent path where relevant;
- a dependency failure.
For every case, state which layer detects the problem and what the caller observes. That level of specificity is expected in a senior code review or technical interview. It also exposes gaps in the implementation: if you cannot say where a malformed value is rejected or how a retry is handled, the contract is not complete yet.
Production perspective
Production correctness is broader than “the code works on my machine.” Ask how the design behaves during deploys, retries, partial failure, stale clients, concurrent requests, malformed data, schema changes, and high-cardinality workloads. Prefer explicit contracts, bounded resource usage, structured errors, and measurable behavior. Optimize only after evidence identifies the bottleneck or risk.
When an external dependency is involved, define a timeout and cancellation strategy. When persistence is involved, define transaction and consistency expectations. When the design includes user-visible state, account for loading, empty, error, stale, and success states. When security is relevant, assume the client can be modified and network input is untrusted. Client behavior is not a substitute for server-side authorization, validation, or persistence guarantees.
Guided lab
Run a 60-minute language mock:
- 15 minutes of JavaScript runtime questions;
- 15 minutes of TypeScript modeling questions;
- 20 minutes of code review and refactoring;
- 10 minutes explaining one event-loop problem and one generic or narrowing problem on a whiteboard.
Complete the lab with this discipline:
- Write the requirement and two non-requirements.
- List input, output, and error contracts before implementing.
- Implement the smallest correct vertical slice.
- Add at least one invalid-input test and one edge-case test.
- Instrument or inspect the behavior instead of guessing.
- Refactor one hidden assumption into an explicit type, constraint, function, or configuration.
- Explain one alternative design and why you did not choose it.
- Record a short “what would break at 10× scale?” note.
The time limit is part of the exercise. It should force prioritization, but not justify skipping the reasoning. If you run out of time, say which contract or test you would complete next and why.
Edge cases and failure modes
- JavaScript execution: Test absence, malformed input, duplicates, ordering and concurrency where applicable, and behavior at the smallest and largest credible sizes.
- Async reasoning: Test absence, malformed input, duplicates, ordering and concurrency where applicable, and behavior at the smallest and largest credible sizes.
- TypeScript boundaries: Test absence, malformed input, duplicates, ordering and concurrency where applicable, and behavior at the smallest and largest credible sizes.
- Advanced typing: Test absence, malformed input, duplicates, ordering and concurrency where applicable, and behavior at the smallest and largest credible sizes.
- Modules: Test absence, malformed input, duplicates, ordering and concurrency where applicable, and behavior at the smallest and largest credible sizes.
These categories overlap on purpose. For example, an absent value can be a runtime problem, a narrowing problem, and a module-configuration problem depending on where it enters the system. Test the boundary where the uncertainty first appears rather than assuming a later layer will repair it.
Common mistakes and debugging
- Solving the example instead of the requirement: a copied pattern can be syntactically correct but architecturally wrong.
- Hiding uncertainty with assertions, broad exception handlers, permissive schemas, or “temporary”
anyvalues. - Testing only the happy path and discovering the real contracts only after integration.
- Optimizing before measuring, or selecting a scalable mechanism without a scale requirement.
- Letting client-side behavior stand in for server-side authorization, validation, or persistence guarantees.
For debugging, reproduce the smallest failing case first. Inspect the actual value or execution plan, trace the boundary where the invariant first becomes false, and fix the layer that owns the problem instead of adding a downstream patch. Depending on the failure, that boundary may be source or build, browser or DOM, Network or HTTP, server or route, database or query, or deployment or configuration.
Interview questions
- What problem does JavaScript execution solve, and what trade-off or failure mode would make you choose a different approach?
- What problem does async reasoning solve, and what trade-off or failure mode would make you choose a different approach?
- What problem do TypeScript boundaries solve, and what trade-off or failure mode would make you choose a different approach?
- What problem does advanced typing solve, and what trade-off or failure mode would make you choose a different approach?
- What problem do modules solve, and what trade-off or failure mode would make you choose a different approach?
Checkpoint
Without notes, explain JavaScript and TypeScript Interview Review to another developer in five minutes. Include one invariant, one edge case, one production failure mode, and one alternative design. Then implement a small example without copying the lesson code. Your explanation and implementation should agree: the invariant you describe should be visible in the types, runtime checks, control flow, or tests.
Mastery checklist
- I can define the core terms precisely.
- I can choose a design from requirements instead of from habit.
- I can implement and test the normal path and edge cases.
- I can explain the runtime, storage, or complexity cost.
- I can identify which layer owns validation, errors, and recovery.
- I can compare at least two reasonable alternatives.
- I can explain how the design changes at larger scale or stricter reliability.
