TypeScript

TypeScript Exercises

Finish confident in building, structuring, and reasoning about JavaScript applications without relying on frameworks.

TypeScript 7.0TypeScript 7.0
Read the TypeScript overview
Module 1

Dom Selection & Manipulation

Master the essentials of working directly with the DOM to build responsive, interactive interfaces without frameworks.

4
About this challenge

Build an entire product card in TypeScript from a product object (name, price, rating, inStock, imageAlt) — create every element, set text content, the image alt, a data-* attribute for the id, and a stock class derived from inStock. Format the price to two decimals. The renderer returns the node without ever touching the document, and two calls with the same product produce two independent nodes.

About this challenge

Implement a banner controller that switches between four states (info, success, warning, error) using classList only, sets the message text via textContent, and updates role and aria-live to match the state. Hiding the banner clears the message and removes every state class so states never overlap.

About this challenge

Build table rows from an array of records using createElement and a DocumentFragment, appending to <tbody> in a single operation. Money cells receive a numeric class; each row carries a data-* id; cell text is set via textContent so a value containing angle brackets renders as text, not markup; and an empty array leaves the <tbody> untouched.

About this challenge

Build one tile per metric from an array (label, value, unit, delta, target), each field in its own slot, with a data-* attribute for the metric key and a trend class derived from the sign of delta (including zero). Tiles whose value is below target carry a below-target marker; an empty array renders a no-data element and no tiles at all.

Module 2

Events & Event Handling

Master event handling to connect user actions with meaningful changes in your application.

4
About this challenge

Wire increment / decrement buttons to a numeric display bound between 1 and a supplied maximum, disabling each button at its bound. Values typed directly into the field are clamped on blur, and a change callback fires only when the value actually changes — not on repeat writes of the same number.

About this challenge

Open and close a dialog element, closing on the close button, on Escape, and on a click of the backdrop but not the panel. Focus moves into the dialog on open and returns to the element that opened it on close, and the key listener is removed on close so a later Escape press does nothing.

About this challenge

Update a counter on every input event showing characters used against a maximum, add a warning class within the last 20 chars, and block further input beyond the limit while still allowing deletion. Pasting an over-length string truncates to the limit. Use the input event, not keydown.

About this challenge

Wire a text input, a category select, and an in-stock checkbox so that changing any of them filters an in-memory array and re-renders the list under AND semantics — two active filters compose, not replace each other. Show a result count and a no-matches message; clearing a filter restores the previous set; the original array is never mutated.

Module 3

Rendering & DOM Construction

Learn to build DOM the way a maintainable frontend does — as pure render functions over typed data instead of ad-hoc jQuery-style mutation. Renderers take data and return fresh nodes, never reach into the document, and stay independently testable without a page. A single mount helper owns container mutation. The topic covers list rendering, exhaustive state rendering (loading / empty / error / list) over discriminated unions, safe text handling that resists XSS by using textContent and URL allow-listing, and template-driven filtered lists that re-render only what should change while the form retains user input. The recurring lesson: separate pure logic from DOM ownership, and every render leaves the container with exactly one child no matter what came before.

4
About this challenge

Implement renderRow, renderList, renderPanel, and mount to turn a typed job array into an Open Positions panel. Renderers must be pure (no document queries, no side effects) and return fresh nodes; renderList([]) returns a documented empty-state node; mount replaces container contents so repeat calls leave exactly one child.

About this challenge

Implement renderFeed and the four state renderers. renderFeed dispatches on a discriminated FeedState union (loading | empty | error | list), swaps the container's single child, and wires an onRetry callback into the error state that fires exactly once per click. Switching between states must leave no residue from the previous state.

About this challenge

Implement renderComment + renderCommentList and the splitLines, truncate, isSafeUrl helpers. Review text is rendered via textContent only so a raw <script> never becomes an element; links are built from a separate url field and only when the scheme is http(s); long text truncates at 240 chars with an expand control that reveals the full body on click.

About this challenge

Implement pure helpers (parseFilters, applyFilters, formatCount) plus renderCard and renderResults. The filter form combines a title search, difficulty select, and max-time cap under AND semantics; submits re-render only the results panel while the form retains submitted values; no-match renders a single empty-state node; cards are built by cloning a <template>.

Module 4

Forms & Input Handling

Build production-shaped form flows in Vanilla TypeScript. The topic covers pure validate functions that return typed error maps, per-field render helpers that manage aria-invalid and aria-describedby, touched-on-blur error surfacing, submit gating tied to live validity, dirty tracking against a baseline with beforeunload prompts, conditional field groups driven by a discriminant that also shape the outgoing payload, and async submission with confirmation and rejection paths. The recurring lesson: pure logic decides what's wrong, small render helpers put that decision on the page, and payloads leaving the form are trimmed, typed, and contain nothing from a hidden field group.

4
About this challenge

Implement visibleFieldNames, validate, renderGroups, renderFieldError, and renderPayload. The insurance-type select reveals one of three groups (Auto / Home / Life); switching type clears the previously visible group's inputs; validation reports errors only for currently visible fields; a valid submit renders a JSON payload with the common fields plus the visible group's fields only.

About this challenge

Implement isDirty, validate, and the render helpers (renderValues, renderFieldError, renderSaveEnabled, renderSavedFlash). Save enables the moment values diverge from the baseline and disables when they match again; a valid save promotes current values to the new baseline; Reset restores the baseline; and the browser's beforeunload prompt fires only while the form is dirty.

About this challenge

Implement validate and the renderFieldError, renderSubmitEnabled, renderSuccess helpers. Errors appear only after a field is blurred, aria-invalid and aria-describedby toggle in lockstep with the message, the submit button tracks live validity across name / email / password (min 8) / confirm, and mismatch is reported on the confirm field only. A valid submit clears the form, re-disables submit, and reveals the success

About this challenge

Implement validate, buildPayload, and the renderFieldError, renderFormDisabled, renderSubmitError, renderConfirmation helpers. On submit, the form validates, normalises raw inputs (trimmed strings, numeric fields as number, ISO date passthrough), disables every input while awaiting window.submitBooking(payload), and either reveals a confirmation with the returned reference or re-enables the form and surfaces the rejection message with typed values preserved.

Module 5

Modules & Code Organisation

Structure a small TypeScript codebase across modules the way a real front end does. The topic covers splitting helpers into cohesive named-export packages with an acyclic import graph and no window writes, centralising configuration behind a single accessor module that hides a raw global from every consumer, dynamic import() for lazy code-splitting with deduped concurrent calls and success caching (but no cached failures), and shared bootstrap plus per-view entries whose init functions are idempotent — safe to call multiple times without duplicating listeners or work. The runtime tests enforce the seams: no side effects at import time, no global mutation, no duplicated registrations on repeat init.

4
About this challenge

Split a monolithic entry into three named-export modules — format (formatPrice, formatSavingsPercent), validate (isValidEmail), and dom (setText, setDisabled) — and wire them from a thin entry inside a single DOMContentLoaded handler. No file may assign to window, importing any helper in isolation must not touch the document, and the helper import graph must have no cycles.

About this challenge

Write config.ts — DEFAULTS, UnknownConfigKeyError, getConfig, and the generically-typed getConfigValue<K>(key) — as the single seam between a raw window.__STOREFRONT_CONFIG__ object and every consumer. Defaults document absent keys, injected values (including false and "") win over defaults, the returned object is frozen, unknown keys throw the named error, and no consumer file references the raw global.

About this challenge

Implement createChartLoader(importFn) and the default loadChart so a heavy chart module is only fetched via import('./chart') when the user clicks Load. The loader dedupes concurrent calls to a single in-flight promise, caches the resolved module for every future call, and does not cache failures so Retry starts clean. The entry never references ./chart statically.

About this challenge

Write bootstrap.ts, homeEntry.ts, and productEntry.ts so nothing runs at import time. initBootstrap() runs its work exactly once no matter how many entries invoke it; each entry's init() writes only to its own view and registers only its own click listener; and any init() is safe to call multiple times without duplicating listeners or double-writing text. Idempotency lives in module-scope let flags; no file assigns to window.

About this challenge

Reduce a bookings array into a map keyed by date, each entry holding its bookings sorted by start time and a count. Every day in the requested range is included — days with no bookings appear with an empty list — keys are returned in chronological order, and a booking outside the range is excluded.

About this challenge

Sort leaderboard entries by score descending with a documented tiebreak (earlier finish time, then name) without mutating the input. Support an ascending toggle and return a position for each entry, sharing a position number across genuine ties. The order is stable across repeated calls.

About this challenge

Compute an order summary from an order-lines array using map, filter, and reduce: item count, subtotal, discount total, tax, and grand total, excluding cancelled lines from every figure. An empty order returns zeros — never NaN or null — and a line with zero quantity contributes nothing. The input array is never mutated.

About this challenge

Transform a stock array into a summary (total value, low-stock count, out-of-stock list, top categories by value) and render each part into the panel. Values render formatted, every derived figure comes from the transform rather than the DOM, and re-running with new data replaces the rendered summary rather than appending.

About this path

This path uses TypeScript in the browser: selecting and changing the DOM, building elements, handling forms, organizing code into modules, and transforming arrays of data. You should know basic JavaScript and HTML before you start. No earlier TypeScript experience is needed. The data module turns raw records into views, such as a leaderboard and an inventory summary panel.

Frequently asked questions

4

What will I practice in this path?

6 modules: Dom Selection & Manipulation, Events & Event Handling, Rendering & DOM Construction, Forms & Input Handling, Modules & Code Organisation and Arrays & Data Transformation.

How many challenges are there, and how hard are they?

24 challenges, all easy.

What does this path run on?

Challenges run on TypeScript 7.0.

Do I need to install anything?

No. Every challenge runs in a browser-based IDE with everything already set up, and your code is graded automatically against a test suite.

We use cookies that are necessary to run Staffcoder, and optional analytics cookies to see which pages get used. Analytics stays off unless you accept. Read our Cookie Policy.