Volume 4

Q209: Template Literal Types and Typed Events

Difficulty: SeniorFrequency: MediumAnswer time: 8-12 minutes

What Interviewers Want To Evaluate

Interviewers want to know whether you can use string-based TypeScript features for real frontend contracts.

They check template literal types, string unions, event names, route names, analytics contracts, key remapping, and the boundary between useful typing and excessive type programming.

Short Interview Answer

Template literal types let TypeScript build string types from other string unions. They are useful for typed events, route patterns, CSS token names, analytics names, and generated handler props. I use them when string conventions are important enough to enforce, but I avoid making string types so clever that developers cannot understand the contract.

Detailed Interview Answer

Frontend systems use strings everywhere.

Examples:

route names
event names
feature flag keys
CSS token names
storage keys
analytics events
permission names
component variants

Template literal types make some of those strings safer.

Basic Template Literal Type

type Volume = "volume-1" | "volume-2" | "volume-3" | "volume-4";
type PagePath = `/${Volume}`;

PagePath becomes:

"/volume-1" | "/volume-2" | "/volume-3" | "/volume-4"

This is useful when the string pattern is predictable.

Event Names

Analytics often has naming conventions.

type Entity = "question" | "batch" | "practice";
type Action = "opened" | "completed" | "reset";

type AnalyticsEventName = `${Entity}_${Action}`;

This creates names like:

question_opened
batch_completed
practice_reset

It prevents random strings from entering event tracking.

Typed Event Payloads

Names alone are not enough.

Payloads matter.

type EventPayloads = {
  question_opened: { questionId: string };
  question_completed: { questionId: string; secondsSpent: number };
  batch_opened: { batchSlug: string };
  practice_completed: { language: "js" | "ts"; success: boolean };
};

Then build a typed tracker.

function trackEvent<TEvent extends keyof EventPayloads>(
  event: TEvent,
  payload: EventPayloads[TEvent],
) {
  console.log(event, payload);
}

Usage:

trackEvent("question_completed", {
  questionId: "Q209",
  secondsSpent: 180,
});

Incorrect payloads fail at compile time.

Generated Handler Names

Template literals pair well with key remapping.

type ChangeHandlers<T> = {
  [Key in keyof T as `on${Capitalize<string & Key>}Change`]: (value: T[Key]) => void;
};

For:

type Settings = {
  theme: "light" | "dark";
  fontSize: number;
};

You get:

type SettingsHandlers = {
  onThemeChange: (value: "light" | "dark") => void;
  onFontSizeChange: (value: number) => void;
};

This is useful for component libraries and generated form helpers.

Intrinsic String Manipulation Types

TypeScript provides helpers:

Uppercase
Lowercase
Capitalize
Uncapitalize

Example:

type Method = "get" | "post";
type HandlerName = `handle${Uppercase<Method>}`;

The result is:

"handleGET" | "handlePOST"

Use these for conventions that matter.

Route Patterns

Template literal types can model route-like strings.

type QuestionId = `Q${number}`;
type QuestionPath = `/questions/${string}`;
type BatchPath = `/batch/volume-${number}-batch-${number}`;

These types are not full route validators.

They are guardrails.

For dynamic runtime strings, validation may still be needed.

CSS Token Names

Design systems can benefit.

type ColorRole = "surface" | "text" | "accent";
type Scale = "100" | "200" | "300";
type TokenName = `color.${ColorRole}.${Scale}`;

This can protect token usage across components.

But large generated unions can slow the compiler.

Keep token systems practical.

Storage Keys

Local storage keys can be typed.

type AppArea = "reader" | "practice" | "settings";
type StorageKey = `learning-studio:${AppArea}:${string}`;

function readStorage(key: StorageKey) {
  return localStorage.getItem(key);
}

This prevents unrelated keys from being passed accidentally.

When Not To Use Template Literal Types

Avoid them when:

the string pattern is not stable
the generated union is huge
runtime validation is still the real problem
the type is harder to understand than the convention
simple string unions are enough

The type should help the team move faster.

Common Mistakes

Common mistakes include:

assuming typed strings validate runtime input
creating enormous string unions
encoding business logic only in types
using template literals for one-off values
forgetting that external data remains untrusted
making analytics contracts too rigid to evolve

Type-level string safety is useful, but it is not a runtime contract by itself.

Senior Trade-Offs

Template literal types are great for conventions.

They are less useful for free-form user input.

Good uses:

analytics event names
route helper outputs
generated handler prop names
design token names
storage key prefixes
permission strings

Weak uses:

arbitrary API strings
large dynamic content
user-entered values
temporary naming conventions

Interview Framing

In an interview, say:

I use template literal types when a string has a stable convention and mistakes would be costly. They work especially well with typed event payloads because the event name can select the correct payload type.

Revision Notes

  • Template literal types build string types from unions.
  • They can enforce naming conventions.
  • Typed event maps connect event names to payloads.
  • String manipulation utilities help generated names.
  • They do not validate runtime strings.
  • Avoid huge or unclear generated unions.

Follow-Up Questions

  • What is a template literal type?
  • How would you type analytics events?
  • How do template literals work with key remapping?
  • Why are typed route strings not full validation?
  • When can template literal types hurt maintainability?

How I Would Answer This In A Real Interview

I would say template literal types are useful when a string convention matters, like analytics names, storage keys, or generated handler props. I would pair them with event payload maps so trackEvent("question_completed", payload) only accepts the right payload. I would also be clear that this only protects compile-time code. Runtime data from APIs, URLs, and users still needs validation.