Typed Forms Without Pain (Mapped Types + Generic Helpers)

A production-friendly pattern to type form values, errors, and validators using mapped types—no giant unions, no any.

F

Frontend Interview Team

March 01, 2026

2 min read
Typed Forms Without Pain (Mapped Types + Generic Helpers)

What you’ll learn

  • How mapped types model values, errors, and touched consistently
  • How to write typed validators that scale
  • A pattern used across React/Next apps (framework-agnostic)

30‑second interview answer

Mapped types let you transform a form’s value type into related types like Errors<T> and Touched<T> while keeping keys in sync. This prevents bugs where you add a field but forget to update error handling. The key idea is: define the form data once, derive everything else from it.


Step 1: Define the form values type once

type SignupValues = {
  email: string;
  password: string;
  marketingOptIn: boolean;
};

type Errors<T> = {
  [K in keyof T]?: string;
};
 
type Touched<T> = {
  [K in keyof T]?: boolean;
};
 
type SignupErrors = Errors<SignupValues>;
type SignupTouched = Touched<SignupValues>;

This keeps keys aligned forever.


Step 3: Typed validators

A common validator shape:

type Validator<T> = (values: T) => Errors<T>;
 
const validateSignup: Validator<SignupValues> = (values) => {
  const errors: Errors<SignupValues> = {};
 
  if (!values.email.includes("@")) errors.email = "Invalid email";
  if (values.password.length < 8) errors.password = "Min 8 characters";
 
  return errors;
};

No casts, no stringly-typed keys.


Step 4: Field-level validators with key-safe access

type FieldValidator<T, K extends keyof T> = (value: T[K], values: T) => string | undefined;
 
type FieldValidators<T> = {
  [K in keyof T]?: FieldValidator<T, K>;
};
 
const fieldValidators: FieldValidators<SignupValues> = {
  email: (email) => (email.includes("@") ? undefined : "Invalid email"),
  password: (pw) => (pw.length >= 8 ? undefined : "Too short")
};

Now the validator signature always matches the field type.


Common mistakes

  • Storing errors as Record<string, string> (loses key safety)
  • Copy-pasting field names into multiple types (guaranteed drift)
  • Over-modeling nested forms before you need it

Production rule of thumb

  • Define Values once.
  • Derive Errors<T>, Touched<T>, validators, and field configs from it.
  • Prefer small, composable validators.

Interview questions

  1. Q: What do mapped types buy you in forms?

    • A: Key-level consistency: add/remove fields once and all derived types update.
  2. Q: How do you type a validator for a specific field?

    • A: Use generics like FieldValidator<T, K> and map it across keys.

Quick recap

  • Mapped types keep form keys consistent.
  • Typed validators scale without string keys.
  • This is high-signal TypeScript that’s actually practical.