TypeScript Full-Stack Interview Cheat Sheets

Fast references for a React + Node coding interview: TypeScript syntax, React hooks, API patterns, async code, data transformations, debugging, and a practical 40-minute workflow.

Interview-use note: This is a static reference sheet, not an AI assistant or code-generation tool. Confirm with the interviewer beforehand that a personal syntax reference is permitted, and disclose it clearly before the exercise begins.

1. TypeScript Core

Types you are most likely to write under time pressure

Basic annotations

const name: string = "Ada";
const count: number = 3;
const ready: boolean = true;

const ids: number[] = [1, 2, 3];
const tags: Array<string> = ["a", "b"];

let result: string | null = null;

function add(a: number, b: number): number {
  return a + b;
}

Object types and interfaces

type User = {
  id: number;
  name: string;
  email?: string;
};

interface ApiResponse {
  users: User[];
  total: number;
}

function displayUser(user: User): string {
  return `${user.id}: ${user.name}`;
}

Union types and narrowing

type Status = "idle" | "loading" | "success" | "error";

function format(value: string | number): string {
  if (typeof value === "number") {
    return value.toFixed(2);
  }
  return value.trim();
}

type Result =
  | { ok: true; data: User[] }
  | { ok: false; error: string };

function read(result: Result) {
  if (result.ok) return result.data;
  console.error(result.error);
  return [];
}

Generics

function first<T>(items: T[]): T | undefined {
  return items[0];
}

async function getJson<T>(url: string): Promise<T> {
  const response = await fetch(url);

  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`);
  }

  return response.json() as Promise<T>;
}

const users = await getJson<User[]>("/api/users");

Utility types

TypeUse
Partial<T>All fields optional
Required<T>All fields required
Pick<T, K>Select fields
Omit<T, K>Remove fields
Record<K, V>Typed object map
type UserPreview = Pick<User, "id" | "name">;
type NewUser = Omit<User, "id">;
type UserPatch = Partial<NewUser>;

const byId: Record<number, User> = {};

Unknown, any, and assertions

  • Prefer unknown when the value truly is not known.
  • Use any only as a temporary escape hatch.
  • Assertions do not validate data at runtime.
function getMessage(error: unknown): string {
  if (error instanceof Error) return error.message;
  return String(error);
}

const input = document.querySelector(
  "#search"
) as HTMLInputElement | null;

const value = input?.value ?? "";
Fast repair strategy: When TypeScript fights you, inspect the inferred type, type the boundary first—props, API response, function return— and avoid spreading any through the application.

2. React + TypeScript

Common component and hook patterns

Typed component props

type UserCardProps = {
  user: User;
  selected?: boolean;
  onSelect: (id: number) => void;
};

function UserCard({
  user,
  selected = false,
  onSelect,
}: UserCardProps) {
  return (
    <button
      aria-pressed={selected}
      onClick={() => onSelect(user.id)}
    >
      {user.name}
    </button>
  );
}

useState

const [count, setCount] = useState(0);

const [query, setQuery] = useState("");

const [user, setUser] = useState<User | null>(null);

const [status, setStatus] =
  useState<Status>("idle");

setCount(current => current + 1);

Use the updater form when the next value depends on the previous value.

useEffect

useEffect(() => {
  let cancelled = false;

  async function load() {
    try {
      setStatus("loading");
      const data = await getJson<User[]>("/api/users");

      if (!cancelled) {
        setUsers(data);
        setStatus("success");
      }
    } catch (error) {
      if (!cancelled) {
        setError(getMessage(error));
        setStatus("error");
      }
    }
  }

  load();

  return () => {
    cancelled = true;
  };
}, []);

Dependencies include values used inside the effect that come from component scope.

useMemo and useCallback

const visibleUsers = useMemo(() => {
  const normalized = query.trim().toLowerCase();

  return users.filter(user =>
    user.name.toLowerCase().includes(normalized)
  );
}, [users, query]);

const handleSelect = useCallback((id: number) => {
  setSelectedId(id);
}, []);

Do not add these automatically. Use them when referential stability or expensive recalculation actually matters.

Events and forms

function handleChange(
  event: React.ChangeEvent<HTMLInputElement>
) {
  setQuery(event.target.value);
}

function handleSubmit(
  event: React.FormEvent<HTMLFormElement>
) {
  event.preventDefault();
  saveUser();
}

Derived state

Prefer calculating values from existing state rather than storing duplicates.

// Better
const completed = tasks.filter(task => task.done);
const completedCount = completed.length;

// Usually avoid
const [completedCount, setCompletedCount] =
  useState(0);

Conditional rendering

if (status === "loading") return <p>Loading…</p>;
if (status === "error") return <p role="alert">{error}</p>;

return (
  <ul>
    {users.length === 0 ? (
      <li>No users found.</li>
    ) : (
      users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))
    )}
  </ul>
);

Immutable updates

setUsers(current =>
  current.map(user =>
    user.id === updated.id ? updated : user
  )
);

setUsers(current =>
  current.filter(user => user.id !== id)
);

setUsers(current => [...current, newUser]);

3. Node.js + API Patterns

Typical Express-style backend work

Basic Express server

import express from "express";

const app = express();
app.use(express.json());

app.get("/api/users", async (_req, res) => {
  const users = await listUsers();
  res.json(users);
});

app.listen(3000, () => {
  console.log("Listening on http://localhost:3000");
});

Typed request data

type CreateUserBody = {
  name: string;
  email: string;
};

app.post(
  "/api/users",
  async (
    req: express.Request<{}, {}, CreateUserBody>,
    res
  ) => {
    const { name, email } = req.body;

    if (!name?.trim() || !email?.trim()) {
      return res.status(400).json({
        error: "Name and email are required",
      });
    }

    const user = await createUser({ name, email });
    return res.status(201).json(user);
  }
);

Route parameters

app.get("/api/users/:id", async (req, res) => {
  const id = Number(req.params.id);

  if (!Number.isInteger(id)) {
    return res.status(400).json({ error: "Invalid id" });
  }

  const user = await findUser(id);

  if (!user) {
    return res.status(404).json({ error: "Not found" });
  }

  return res.json(user);
});

Error handling

app.get("/api/users", async (_req, res) => {
  try {
    const users = await listUsers();
    return res.json(users);
  } catch (error) {
    console.error(error);
    return res.status(500).json({
      error: "Unable to load users",
    });
  }
});

For a small interview task, clear local error handling is usually better than building elaborate infrastructure.

HTTP status shorthand

StatusMeaning
200Successful read/update
201Created
204Success, no body
400Invalid request
404Resource not found
409Conflict or duplicate
500Unexpected server failure

Fetch from React

async function createUser(input: NewUser): Promise<User> {
  const response = await fetch("/api/users", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify(input),
  });

  if (!response.ok) {
    const body = await response.json().catch(() => null);
    throw new Error(body?.error ?? "Request failed");
  }

  return response.json() as Promise<User>;
}

4. Data Transformation Patterns

Likely interview operations

Filter

const active = users.filter(
  user => user.active
);

Map

const names = users.map(
  user => user.name
);

Find

const user = users.find(
  user => user.id === id
);

Some / Every

const hasAdmin = users.some(
  user => user.role === "admin"
);

const allActive = users.every(
  user => user.active
);

Reduce to lookup

const byId = users.reduce<Record<number, User>>(
  (acc, user) => {
    acc[user.id] = user;
    return acc;
  },
  {}
);

Sort without mutation

const sorted = [...users].sort(
  (a, b) => a.name.localeCompare(b.name)
);

Unique values

const uniqueTags = [
  ...new Set(users.flatMap(user => user.tags))
];

Group by property

const byRole = users.reduce<Record<string, User[]>>(
  (acc, user) => {
    (acc[user.role] ??= []).push(user);
    return acc;
  },
  {}
);

Null-safe access

const city =
  user.address?.city ?? "Unknown";

5. Debugging Under Interview Pressure

Keep the system observable

Read the project first

  • Open package.json and identify scripts.
  • Find the application entry points.
  • Run the app before changing anything.
  • Inspect existing types, naming, and patterns.
  • Check browser console and terminal output.

Useful commands

node --version
npm install
npm run dev
npm test
npm run lint
npm run typecheck
npx tsc --noEmit

Use only scripts actually present in the repository.

Log boundaries, not everything

console.log("request body", req.body);
console.log("response status", response.status);
console.log("users received", users.length);

Remove noisy logs once the bug is understood.

Common failure points

  • Frontend calling the wrong route or port.
  • Missing Content-Type: application/json.
  • Forgetting express.json().
  • Not awaiting a promise.
  • Mutating React state directly.
  • Effect dependency loops.
  • String route params treated as numbers.
  • Assuming API data matches a TypeScript type.

6. A 40-Minute Interview Workflow

Optimize for a working vertical slice

Minutes 0–5: Orient

  • Restate the requested behavior.
  • Run the starter app unchanged.
  • Locate frontend, backend, and shared types.
  • Identify the smallest complete path.

Minutes 5–10: Establish the contract

  • Define or inspect the data shape.
  • Confirm route, method, request, and response.
  • Note edge cases without overbuilding.

Minutes 10–27: Build the vertical slice

  • Implement the backend behavior.
  • Call it from the frontend.
  • Render the result.
  • Keep naming explicit and code small.

Minutes 27–34: Handle obvious states

  • Loading.
  • Error.
  • Empty result.
  • Simple input validation.

Minutes 34–38: Verify

  • Exercise the happy path.
  • Try one bad input.
  • Check browser and terminal errors.
  • Run available typecheck/tests.

Minutes 38–40: Explain

  • Summarize what works.
  • Name any unfinished edge case.
  • Explain the next improvement you would make.
Say what you are doing: “I’m going to get one complete path working first, then add validation and polish.” Interviewers can evaluate your reasoning only when you make it visible.

Final pre-interview checklist

  • Node 22+ installed
  • Git credentials working
  • GitHub clone tested
  • Editor AI and autocomplete assistants disabled
  • Copilot, Codeium, Tabnine, etc. disabled
  • Voice assistants disabled
  • Browser tabs cleaned up
  • Terminal font readable on screen share
  • This reference opened locally
  • Interviewer told about the static cheat sheet
  • Water and charger nearby
  • Practice app successfully run with Node 22