Overview
Type parameters, returns, callbacks, overloads, rest parameters, and optional parameters.
TS Functions helps you describe intent in code so mistakes are caught earlier. Treat types as documentation that the compiler can verify, not as noise added after the fact.
Core Ideas
- Use TS Functions to make data shapes and function contracts explicit.
- Prefer precise types over broad ones, especially at module boundaries.
- Narrow unknown input before using it.
- Let compiler errors guide better modeling instead of disabling checks.
Step by Step
- Write the JavaScript shape that TS Functions describes.
- Add the narrowest useful type annotation.
- Use the value in a function so the compiler can verify the contract.
- Intentionally break the shape once, then read the compiler error.
Beginner Explanation
TS Functions teaches how functions communicate: what input they accept, what they return, and what errors or promises they may produce.
Typed functions are especially useful because most bugs happen when one part of the code calls another part with the wrong shape.
Beginners should type parameters first, then return values when the function boundary matters.
Before You Start
- Before practicing TS Functions, write the JavaScript value or function you want to describe.
- Identify the boundary: function parameter, return value, object shape, API response, component prop, or configuration option.
- Turn on strict compiler settings when possible so weak assumptions become visible.
- Change one type at a time and read the compiler message before fixing it.
- Remember that TypeScript checks code before runtime; runtime validation is still needed for untrusted external data.
Key TypeScript Concepts
- Function parameters should describe required, optional, default, and rest inputs.
- Return types matter most for exported functions and public APIs.
- Callbacks should include parameter and return expectations.
- Promise<T> describes the resolved value of async work.
Plain-English Glossary
- Type annotation: a written type such as string, number, Lesson, or Promise<User>.
- Inference: TypeScript deciding a type from the value or usage.
- Union: a type that allows one of several choices.
- Narrowing: checking a value so TypeScript can treat it as a more specific type.
- Generic: a reusable type parameter such as T that preserves information.
- Interface: an extendable object contract.
- Type alias: a name for any type expression, including unions and object shapes.
- tsconfig: the project file that controls compiler behavior.
What You Will Learn
- Explain what kind of mistake TS Functions helps TypeScript catch.
- Write a small typed example and predict whether the compiler should accept or reject it.
- Read at least one compiler error and identify the expected type and actual type.
- Refactor the example so the type describes the real data more clearly.
Where You Use This in Real Projects
You use TS Functions in forms, API clients, React components, Node.js services, configuration files, data models, reusable utilities, validation layers, tests, and shared packages.
TypeScript is most valuable at boundaries where values move between files, functions, packages, APIs, users, databases, and UI components.
A practical team uses TypeScript to reduce accidental misuse, improve editor autocomplete, and make refactoring less risky.
Compiler and Tooling Notes
- Use strict mode for new code when possible.
- Do not turn a compiler error into any unless you understand what safety is being removed.
- Remember that TypeScript types disappear at runtime after compilation.
- Validate JSON, form data, URL params, and external API responses at runtime before trusting them.
- When a type becomes hard to read, split it into named aliases and test it with small examples.
Beginner Mental Model
Think of TS Functions as a contract between the function author and every caller.
The parameter types describe what the function needs, and the return type describes what the caller can trust after it runs.
Common Compiler Errors and Fixes
- Type mismatch: the value used in TS Functions does not match the expected type. Fix the value or correct the type model.
- Missing property: an object does not include a required field. Add the field, make it optional only if it is truly optional, or use a different type.
- Possibly null or undefined: check the value before using it, use optional chaining, or provide a fallback.
- Unsafe any: replace any with a specific type or unknown plus a narrowing check.
- No matching overload or call signature: compare the arguments you passed with the function type and adjust the call.
Real-World Pattern
type Validator<T> = (value: unknown) => value is T;
function parseWith<T>(value: unknown, validator: Validator<T>): T | null {
return validator(value) ? value : null;
}
- This TS Functions pattern uses a small type at a real boundary instead of adding types everywhere at random.
- The example keeps runtime code readable while giving the compiler enough information to catch mistakes.
- You can reuse the same pattern in API clients, form handlers, components, services, tests, and shared utilities.
Code Example
type Formatter = (value: string) => string;
function formatLesson(title: string, minutes = 30, formatter?: Formatter): string {
const safeTitle = formatter ? formatter(title) : title.trim();
return `${safeTitle} takes ${minutes} minutes.`;
}
async function loadTitle(): Promise<string> {
return 'Async TypeScript';
}
Another Example
type Result<T> =
| { ok: true; data: T }
| { ok: false; error: string };
async function loadLesson(slug: string): Promise<Result<string>> {
if (!slug) return { ok: false, error: 'Missing slug' };
return { ok: true, data: slug.toUpperCase() };
}
More Practice Examples
Example 1: Model lesson data
type Level = 'beginner' | 'intermediate' | 'advanced';
interface Lesson {
slug: string;
title: string;
level: Level;
minutes: number;
}
const lesson: Lesson = {
slug: 'ts-practice',
title: 'TypeScript Practice',
level: 'beginner',
minutes: 30
};
- The Level union prevents accidental values such as easy or expert.
- The Lesson interface names the object shape once so it can be reused.
- The compiler checks missing properties and wrong value types before runtime.
Example 2: Narrow unknown input
function formatTitle(value: unknown): string {
if (typeof value === 'string' && value.trim() !== '') {
return value.trim();
}
return 'Untitled lesson';
}
console.log(formatTitle(' TS Functions '));
- unknown forces the function to check the value before using string methods.
- The return type tells callers they will always receive a string.
- This pattern is useful for form input, JSON, and third-party data.
Example 3: Reuse a generic response type
type ApiResult<T> =
| { ok: true; data: T }
| { ok: false; error: string };
function unwrap<T>(result: ApiResult<T>): T {
if (!result.ok) throw new Error(result.error);
return result.data;
}
- The generic T preserves the data type through the helper.
- The union makes success and failure explicit.
- The if statement narrows result before data is returned.
Beginner Practice Examples
Practice: Type a callback
type Lesson = { title: string; minutes: number };
type Filter = (lesson: Lesson) => boolean;
function findLessons(lessons: Lesson[], filter: Filter): Lesson[] {
return lessons.filter(filter);
}
- The callback receives one Lesson and must return true or false.
- The returned array still contains Lesson objects.
- This pattern appears in filters, event handlers, validators, and mappers.
Practice: Add a safe formatter
function minutesLabel(minutes: number | null | undefined): string {
if (minutes == null) {
return 'Time not set';
}
return `${minutes} minutes`;
}
console.log(minutesLabel(35));
- The parameter allows number, null, or undefined.
- The null check handles both null and undefined.
- After the check, TypeScript knows minutes is a number.
Practice: Type a small API result
type Result<T> =
| { ok: true; data: T }
| { ok: false; error: string };
const result: Result<string[]> = {
ok: true,
data: ['HTML', 'CSS', 'TypeScript']
};
- The generic result can work with any data type.
- The ok field acts as the discriminant for narrowing.
- Failure results must include error instead of data.
Compiler Error Practice
Error 1: Wrong value type
Broken version
type Lesson = {
title: string;
minutes: number;
};
const lesson: Lesson = {
title: 'TS Basics',
minutes: '30'
};
Fixed version
type Lesson = {
title: string;
minutes: number;
};
const lesson: Lesson = {
title: 'TS Basics',
minutes: 30
};
- The broken version uses a string where the type requires a number.
- The fixed version changes the value instead of weakening the type.
- This is the kind of mistake TS Functions should catch before runtime.
Error 2: Possibly undefined value
Broken version
const lessons = ['HTML', 'CSS'];
const first = lessons[2];
console.log(first.toUpperCase());
Fixed version
const lessons = ['HTML', 'CSS'];
const first = lessons[2];
if (first) {
console.log(first.toUpperCase());
} else {
console.log('No lesson found');
}
- The array lookup may not find an item.
- The fixed version checks the value before using a string method.
- This habit helps with arrays, maps, query selectors, and API results.
Error 3: Unsafe unknown value
Broken version
function printTitle(value: unknown) {
console.log(value.trim());
}
Fixed version
function printTitle(value: unknown) {
if (typeof value === 'string') {
console.log(value.trim());
return;
}
console.log('Untitled');
}
- unknown cannot be used like a string until it is narrowed.
- The typeof check proves the value is a string.
- Use this pattern whenever data comes from outside your typed code.
Example Explained
- The TS Functions example starts with a real JavaScript value, function, object, or project setting.
- The type describes the smallest useful contract for that value.
- The compiler can then compare how the value is created, passed, and used.
- When the example narrows a value, TypeScript allows safer access only after the check.
- The best examples keep runtime behavior and type behavior easy to explain separately.
How to Read This Example
- Find the value, function, class, object, or configuration option being typed.
- Read the type from left to right and name what values are allowed.
- Look for optional properties, unions, generics, or narrowing checks.
- Change one property or argument to the wrong type and predict the compiler error.
- Fix the TS Functions example by improving the type model, not by hiding the error with any.
Checklist
- Let the compiler guide the design instead of silencing errors.
- Prefer unknown over any when input is not trusted yet.
- Keep shared types close to the data they describe.
Common Mistakes
- Using any to silence errors instead of modeling the data.
- Typing only the easy parts and leaving unsafe boundaries unchecked.
- Duplicating types instead of deriving them with utility types or generics.
Do and Don't
- Do: use TS Functions to describe real data and real function contracts.
- Do: prefer unknown over any when data has not been checked yet.
- Do: let inference help with local variables when the value is obvious.
- Don't: add complicated types that make the code harder to use than the bug they prevent.
- Don't: use type assertions to silence errors before proving the value is safe.
Practice Challenge
Type the TS Functions example, intentionally break one value, and read the compiler message before fixing it.
Try These Changes
- Add one missing property to the example and read the compiler error.
- Change one string literal to an invalid value and explain why TypeScript rejects it.
- Replace any with unknown, then add a narrowing check.
- Extract an inline object type into a named interface or type alias.
- Write one function that accepts the type and returns a formatted display string.
Step-by-Step Project Path
- Create one small file named after TS Functions and write a plain JavaScript version first.
- Add the first type at the boundary: function parameter, object shape, API result, component props, or config value.
- Intentionally break one value so you can read the compiler error in context.
- Fix the value or improve the type without using any as a shortcut.
- Add one runtime check if the value could come from a user, API, URL, file, or third-party package.
- Write a short comment for yourself explaining which bug the type prevents.
Quick Check
- Question: Do TypeScript types exist at runtime? Answer: No, they are checked before the JavaScript runs.
- Question: Why is unknown safer than any? Answer: unknown requires narrowing before you use the value.
- Question: What should you type first? Answer: Boundaries such as parameters, return values, API data, and props.
- Question: What is inference? Answer: TypeScript deciding a type from the value or context.
- Question: How should you fix most TS Functions errors? Answer: Adjust the value or model the type more accurately.
Debugging Checks
- Read the first compiler error carefully before changing multiple files.
- Identify the expected type and the actual type in the error message.
- Hover values in the editor to inspect inferred types.
- Check tsconfig strictness, module settings, lib settings, and included files when errors seem surprising.
- Use small temporary variables to make complicated inferred types easier to inspect.
Mini Project
Build a typed API helper for TS Functions: typed parameters, return type, callback, async Promise result, and explicit error result.
Mastery Check
- You can explain what error TS Functions catches before runtime.
- You can narrow unknown data before using it.
- You can reuse the type safely in another function or module.