Skip to content
Skilled.dev

Language preparation

TypeScript coding interview preparation

Use precise types for maps, trees, unions, and asynchronous functions while keeping your interview implementation readable and testable.

Published · 5 min read

TypeScript interview preparation combines algorithm practice with a clear model of the values your code accepts and returns. Useful types make missing cases visible and explain a function's contract. They should leave enough time to solve, test, and discuss the problem itself.

Prepare using the TypeScript version and compiler settings available in the interview environment. A browser editor may differ from your usual project. The examples here use ordinary interfaces, generics, maps, and discriminated unions; they do not depend on a framework or a recent experimental language feature.

Begin with the runtime algorithm

TypeScript runs with JavaScript's runtime data structures and numeric behavior after type checking and compilation. A Map<number, number> is still a map. An array sort still needs an appropriate numeric comparator. A recursive function still consumes runtime stack space.

Use the JavaScript preparation guide for those implementation details. Then add types that make your assumptions inspectable: whether a result may be absent, whether an input can be mutated, and whether a tree child can be missing.

Avoid using any to silence a type error before understanding it. The error may expose a real missing branch, such as an unsuccessful lookup or an empty tree. A local explicit annotation is often enough; elaborate generic abstractions are rarely the first step in a short interview function.

Write an honest return type

Two Sum can return a tuple of indices or null when no solution exists. That shape communicates that a successful result always contains exactly two indices.

function twoSum(values: readonly number[], target: number): [number, number] | null {
  const previous = new Map<number, number>();
  for (let index = 0; index < values.length; index += 1) {
    const earlier = previous.get(target - values[index]);
    if (earlier !== undefined) return [earlier, index];
    previous.set(values[index], index);
  }
  return null;
}

The map stores numbers, so undefined is available as the missing-result marker in this contract. Comparing earlier !== undefined preserves index zero. If undefined were a valid stored value, a separate has check would be needed to distinguish presence.

The readonly array annotation prevents this function from mutating the input through that parameter in checked code. It does not freeze the actual array at runtime, and it does not make other references to the same object immutable. State what the type expresses instead of promising stronger runtime guarantees.

Model recursive structures with nullable children

A binary-tree node can describe its empty-child case directly. Use a domain-specific name such as BinaryNode to avoid confusion with browser DOM types.

interface BinaryNode<T> {
  value: T;
  left: BinaryNode<T> | null;
  right: BinaryNode<T> | null;
}

function maxDepth<T>(root: BinaryNode<T> | null): number {
  if (root === null) return 0;
  return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
}

The early return handles the empty tree. After that branch, the checker can treat root as a node. The recursive calls still receive nullable children, which matches the base case.

A generic value type is useful because depth does not depend on the payload. Requiring a number there would add an unrelated constraint. By contrast, a binary search tree needs an ordering operation or a comparable value model. A generic parameter alone does not supply a valid comparison between arbitrary values.

The course's exercise harness uses its own published node fields and signatures. Adapt to those contracts when solving its lessons instead of assuming every problem uses this illustrative interface.

Use unions for outcomes with different data

A discriminated union makes the successful and unsuccessful cases explicit. Each variant carries only the fields valid for that outcome.

type SearchResult<T> =
  | { found: true; value: T; index: number }
  | { found: false };

function describeResult(result: SearchResult<string>): string {
  if (!result.found) return 'No match';
  return `${result.value} at index ${result.index}`;
}

Inside the successful branch, the value and index are available together. An object with optional value and optional index would admit incomplete combinations unless another rule excluded them.

This is useful when the interview problem involves state transitions or API results. It can be unnecessary for a small numeric helper where a tuple-or-null return is already clear. Match the model to the actual output rather than introducing a union into every function.

The TypeScript narrowing handbook documents how checks refine union types. The important habit in an exercise is making a real control-flow check that corresponds to the runtime case.

Validate unknown data at a boundary

A type assertion such as JSON.parse(text) as Graph does not inspect the parsed values. If an exercise accepts arbitrary JSON, check the structure before treating it as an adjacency list. That might mean verifying an object, neighbor arrays, and the allowed node-name type.

Inside a pure algorithm exercise, the interviewer may guarantee valid input. You can accept that contract and avoid writing an entire parser. State the assumption explicitly. Boundary validation and algorithm logic deserve separate treatment when they have different responsibilities.

For a union of input shapes, check the discriminant or a supported structural property before accessing variant-specific fields. For optional values, define whether absence, null, an empty string, and zero have different meanings. Truthiness is a runtime rule, not a universal missing-value test.

Keep mutation contracts explicit

If an algorithm sorts, partitions, or writes a result prefix into an input array, its signature and explanation should permit that mutation. If you copy first, include the allocation in the complexity analysis.

A shallow copy of an array preserves references to the same element objects. Sorting the copied array reorders that copy, but editing an object through it can still affect an object visible through the original array. Readonly annotations also do not imply recursively readonly object graphs unless the nested types express that restriction.

For backtracking, distinguish the mutable working path from the saved output. The types of both can be arrays while their lifetimes differ. Copying at a result boundary is a correctness decision that a broad array type cannot enforce by itself.

Practice asynchronous signatures without hiding behavior

An async function returns a promise of its result type. That annotation says what a fulfillment value looks like; it does not eliminate rejection. A caller still needs an error-handling policy when failures are possible.

When implementing a promise aggregator, preserve tuple positions only if that is part of the requested type-level exercise. A simpler homogeneous Promise<T[]> contract can be sufficient for an algorithm interview. Highly precise inference can become a separate problem from correct runtime aggregation.

Read Promise.all implementation for ordering, empty input, and rejection behavior. Implement and test the runtime contract before making the signature more sophisticated.

A useful practice sequence

Solve map-based Two Sum, bracket validation, linked-list reversal, and maximum tree depth. Type their inputs and absent-result cases before running them. Then implement graph traversal with an explicit adjacency-list type and a visited set.

Add one asynchronous utility such as debounce or promise aggregation. Practice explaining the relationship between a callback's type and when it executes. Finish a timed rehearsal by removing unnecessary assertions and checking whether each remaining assertion is justified by a real invariant.

Use the 30-day plan for a broader sequence. When reviewing a failure, record whether it came from an incomplete algorithm, a mistaken runtime assumption, or a type that described more than the implementation guaranteed.

Worked lesson walkthroughs

66 reading lessons with code examples in this language.

Concepts

Concept lesson

Arrays in TypeScript

Learn array insertion in TypeScript. Trace a copied result, explain shifting costs, and check empty inputs and endpoint positions without changing the original.

Concept lesson

Big O time and space complexity in TypeScript

Analyze time and space complexity in TypeScript with a suffix-copy example. Count nested work, returned storage, and allocation instead of counting loops alone.

Concept lesson

Hash Tables in TypeScript

Count frequencies with a hash map in TypeScript. Preserve input order, distinguish missing keys from zero, and explain expected lookup time and retained storage.

Concept lesson

Strings in TypeScript

Practice string construction in TypeScript with a repeat-text example. Explain builders, copies, Unicode representation, output size, and boundary cases.

Concept lesson

Palindromes, permutations, and anagrams in TypeScript

Check an ASCII-normalized palindrome in TypeScript. Trace two-pointer comparisons, case handling, ignored characters, and the limits of the text contract.

Concept lesson

Recursion in TypeScript

Trace recursive factorial in TypeScript from the base case through returning calls. Explain termination, stack space, integer limits, and boundary tests.

Concept lesson

Logarithmic time in TypeScript: repeated halving and search

Understand logarithmic loops in TypeScript by counting integer halvings. Test powers of two, explain rounding, and derive the exact step count.

Concept lesson

Binary search in TypeScript: invariants and boundary tests

Write first-occurrence binary search in TypeScript. Trace half-open bounds, duplicate values, missing targets, and the random-access assumption behind O(log n).

Concept lesson

Sorting in TypeScript

Return a numerically sorted copy in TypeScript. Preserve duplicates and the original input, choose the right library operation, and account for copied storage.

Concept lesson

Linked Lists in TypeScript

Find a linked list's middle in TypeScript with slow and fast runners. Define the even-length result, handle an empty list, and separate construction from traversal.

Concept lesson

Pointers in TypeScript

Trace shared mutation and reference rebinding in TypeScript. Follow two aliases through one object update and a new allocation, with explicit ownership notes.

Concept lesson

Queues in TypeScript

Practice FIFO queue behavior in TypeScript. Compare efficient front removal with a head cursor, preserve repeated values, and count fill-and-drain costs.

Concept lesson

Stacks in TypeScript

Practice LIFO stack behavior in TypeScript. Trace push and pop, separate empty state from zero payloads, and explain node ownership and end-operation costs.

Concept lesson

Trees in TypeScript

Compute binary-tree edge height in TypeScript. Read heap-indexed nullable slots, handle an empty root, and account for reachable nodes and recursion depth.

Concept lesson

Tree traversal in TypeScript: in-order, pre-order, and post-order

Trace in-order binary-tree traversal in TypeScript. Explain nullable child positions, visit order, recursion space, and when the output is actually sorted.

Concept lesson

Binary Search Trees in TypeScript

Search a binary search tree in TypeScript. Follow one ordered path, handle absent children, and relate search cost to height without assuming balance.

Concept lesson

Graphs in TypeScript

Read graph adjacency lists in TypeScript and compute out-degrees. Define vertex IDs, parallel edges, self-loops, representation costs, and input assumptions.

Concept lesson

Depth-First Search in TypeScript

Implement iterative DFS in TypeScript. Preserve listed neighbor order, skip repeated visits, trace a shared descendant, and explain pending-stack costs.

Concept lesson

Breadth-First Search in TypeScript

Implement BFS in TypeScript with FIFO order and enqueue-time discovery. Trace graph layers, cycles, shared descendants, and visited-storage costs.

Concept lesson

Subproblems and Overlapping Subproblems in TypeScript

Count naive Fibonacci calls in TypeScript. Trace overlapping states, include base-case work, and identify the cache key before applying memoization.

Concept lesson

Memoization in TypeScript

Memoize Fibonacci in TypeScript and count distinct computed states. Preserve cached zero, define cache lifetime, and explain remaining recursion depth.

Concept lesson

Bottom-Up vs. Top-Down in TypeScript

Build a bottom-up Fibonacci table in TypeScript. Follow dependency order, test zero input, and compare full-table storage with rolling-state alternatives.

Concept lesson

Dynamic Programming in TypeScript

Solve minimum coin count in TypeScript. Trace the recurrence, represent unreachable states, test a greedy counterexample, and explain amount-based table costs.

Data structure implementations

Implementation tutorial

Implement a dynamic array in TypeScript

Implement a map-backed teaching array in TypeScript. Trace push, pop, insertion, and removal, including logical length, missing indices, and obsolete entries.

Implementation tutorial

Implement a hash table in TypeScript: collisions, updates, and removal

Build a hash table in TypeScript with separate chaining. Follow colliding keys through lookup, update, and deletion, and explain the limits of a fixed teaching hash.

Implementation tutorial

Implement a linked list in TypeScript: head, tail, and node identity

Build a doubly linked list in TypeScript. Trace prepend and append, verify reciprocal links and both endpoints, and explain ownership and retention.

Implementation tutorial

Implement a queue in TypeScript: FIFO links and empty transitions

Implement a linked queue in TypeScript. Trace FIFO order, the last removal, and refilling an empty queue while preserving first and last endpoint invariants.

Implementation tutorial

Implement a stack in TypeScript: push, pop, and ownership

Implement a linked stack in TypeScript. Follow the top link through push, peek, and pop, including empty state, returned nodes, and retained chains.

Implementation tutorial

Implement a binary search tree in TypeScript: insert and search

Build a binary search tree in TypeScript. Trace insertion and search, preserve duplicate keys on the right, and test skewed shapes without assuming balancing.

Implementation tutorial

Implement a graph in TypeScript: adjacency lists and edge removal

Build an adjacency-list graph in TypeScript. Trace directed and undirected edges, self-loops, parallel entries, and removal of incoming and outgoing edges.

Practice problems

Practice walkthrough

Two Sum in TypeScript: all index pairs with typed Maps

Return every matching index pair using Map<number, number>, tuple results, strict null checks, and safe index handling in TypeScript.

Practice walkthrough

Valid brackets in TypeScript: a typed stack for matching delimiters

Validate nesting in TypeScript with an explicitly typed stack. Handle missing pop results, ignore ordinary text, and test crossing brackets.

Practice walkthrough

Product of array except self in TypeScript: prefix and suffix passes with strict indexing

Build products without division in TypeScript. Use strict array access, explain prefix/suffix invariants, and test zeros and numeric limits.

Practice walkthrough

Counting sort for ages in TypeScript: worked solution

Sort bounded ages with a frequency table, preserving duplicate values. Follow the TypeScript implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Best stock profit in TypeScript: one transaction

Find the best single chronological stock trade with one scan. Follow the TypeScript implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Most frequent email in TypeScript: hash-map solution

Count email occurrences and return the unique most frequent address. Follow the TypeScript implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

First Bad Version in TypeScript: boundary binary search

Find the first true value in a monotone version predicate. Follow the TypeScript implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Reverse a linked list in TypeScript: pointer walkthrough

Reverse a linked list by moving nodes from an untouched suffix to a reversed prefix. Follow the TypeScript implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Remove kth node from end in TypeScript: zero-based deletion

Delete the zero-based kth node from the end of a linked list. Follow the TypeScript implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Merge sorted linked lists in TypeScript: Combine Blockchains

Merge two sorted linked chains while preserving duplicate timestamps. Follow the TypeScript implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Count Islands in TypeScript: DFS flood-fill walkthrough

Count side-connected land components with a destructive flood fill of the working grid. Follow the TypeScript implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Validate a binary search tree in TypeScript: inherited bounds

Validate every binary-tree node against bounds inherited from all its ancestors. Follow the TypeScript implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Binary tree maximum depth in TypeScript: count edges

Measure a binary tree’s longest root-to-leaf path in edges. Follow the TypeScript implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Lowest common ancestor in TypeScript: return the original node

Find the lowest common ancestor in a general binary tree and return the original node. Follow the TypeScript implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Generate string permutations in TypeScript: positional results

Generate positional string permutations, retaining repeated results for repeated characters. Follow the TypeScript implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Group palindrome permutations in TypeScript: counts and keys

Filter palindrome permutations, then group retained strings by character multiset. Follow the TypeScript implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Merge intervals in TypeScript: Interview Scheduler

Merge sorted interval coverage with an explicit touching-endpoint rule. Follow the TypeScript implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Min-max stack in TypeScript: constant-time extrema

Support stack extrema through histories that retain duplicate record values. Follow the TypeScript implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

BFS shortest path in TypeScript: Pilot a Rocket

Find an unweighted shortest route and reconstruct it from predecessor links. Follow the TypeScript implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Maze reachability in TypeScript: memoized down/right search

Memoize directed maze reachability while keeping unknown distinct from false. Follow the TypeScript implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Search a rotated sorted array in TypeScript: Shifted Search

Search a distinct rotated sorted array by identifying an ordered half. Follow the TypeScript implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Search a sorted matrix in TypeScript: staircase walkthrough

Eliminate a row or column at each step in a row-and-column sorted matrix. Follow the TypeScript implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

House Robber in TypeScript: maximum nonadjacent sum

Maximize a nonadjacent selection with two rolling prefix optima. Follow the TypeScript implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Sum decimal digits in TypeScript: recursive solution

Sum decimal digits through a decreasing recursive integer decomposition. Follow the TypeScript implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Count 1, 2, or 3-step paths in TypeScript: Three Moves

Count ordered one-, two-, and three-step routes with rolling dynamic programming. Follow the TypeScript implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Coin change in TypeScript: minimum-coin dynamic programming

Minimize unlimited coin usage with a bottom-up amount table. Follow the TypeScript implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Validate Sudoku in TypeScript: rows, columns, and subsquares

Validate filled Sudoku entries against rows, columns, and subsquares. Follow the TypeScript implementation, its correctness argument, worked example, complexity, and boundary tests.

Practice walkthrough

Spiral matrix traversal in TypeScript: Walk a Matrix

Traverse a rectangle in clockwise spiral order using shrinking boundaries. Follow the TypeScript implementation, its correctness argument, worked example, complexity, and boundary tests.

Language behavior and callbacks

Language behavior

JavaScript runtime interview concepts for TypeScript

Practice runtime validation in TypeScript. Narrow an unknown score, reject coercion and nonfinite values, and distinguish executable checks from static types.

Language behavior

TypeScript interview questions: scope, identity, and runtime behavior

Practice TypeScript scope, identity, and equality questions with an executable quiz. Explain closure bindings, object mutation, reassignment, and NaN comparisons.

Language behavior

Fix this and closure bugs in TypeScript

Fix receiver and closure bugs in TypeScript. Bind a presenter callback, preserve per-iteration seat positions, and test what changes when captured objects mutate.

Language behavior

TypeScript array methods: map, filter, find, some, and every

Practice map, filter, find, includes, some, and every in TypeScript. Compare result shapes, empty-array behavior, short-circuiting, and input preservation.

Language behavior

Build a teaching promise in TypeScript: callbacks and scheduling

Build a teaching promise in TypeScript. Store one settlement, schedule early and late observers, catch executor errors, and distinguish it from native chaining.

Language behavior

Build a publish-subscribe dispatcher in TypeScript

Build a publish-subscribe dispatcher in TypeScript. Define duplicate subscriptions, disposal, snapshot delivery, callback errors, and retained-listener costs.

Language behavior

Implement debounce in TypeScript: latest calls, cancel, and flush

Implement trailing debounce in TypeScript with cancel and flush. Preserve the latest arguments and receiver, trace deadlines, and handle callback reentry.

Language behavior

Implement throttle in TypeScript: leading and trailing calls

Implement leading-and-trailing throttle in TypeScript. Keep fixed cooldown windows, deliver the latest pending call, preserve the receiver, and cancel safely.