Functions in TypeScript
Learn how to define and type functions in TypeScript - parameters, return types, optional and default arguments, recursion, closures, higher-order functions, generics, and overloads
Functions are the primary unit of reuse in TypeScript. Because TypeScript is a multi-paradigm language — object-oriented, functional, and imperative all at once — functions are first-class values: you can store them in variables, pass them as arguments, return them from other functions, and assign them types just like any other value.
What sets TypeScript apart from plain JavaScript is that every part of a function can be typed: its parameters, its return value, and the function itself. The compiler then checks every call site. With TypeScript’s structural and optional typing, you get as much or as little type safety as you want — you can annotate everything explicitly, or lean on inference and only annotate the parts that matter.
This tutorial covers function declarations, optional and default parameters, rest parameters, arrow functions and function types, recursion, variable scope and closures, higher-order functions, generics, and function overloads. By the end you’ll know how to express most function patterns you’ll meet in real TypeScript codebases.
Declaring Functions, Parameters, and Return Values
A function declaration names its parameters with types and declares a return type after the parameter list. TypeScript also supports optional parameters (age?), default parameters (role = "member"), and rest parameters (...values) that collect a variable number of arguments into a typed array.
Create a file named functions.ts:
| |
A few things worth noting:
multiplyshows a function type —(a: number, b: number) => number— assigned to a variable. Because the type is declared on the left, the arrow function’s parameters don’t need their own annotations; TypeScript infers them.firstElement<T>is generic. The caller never writes the type — TypeScript infersTasstringfrom["a", "b"], and the return type becomesstring | undefined.applyTwicetakes another function as a parameter, the essence of a higher-order function.
Scope and Closures
TypeScript uses lexical scoping. Variables declared with let and const are block-scoped, and an inner function “closes over” the variables in the scope where it was defined — even after the outer function has returned. This closure is what makes makeCounter below remember its count between calls.
Create a file named closures_scope.ts:
| |
Each call to counter() increments the same captured count, so the values climb. The : void return type on scopeDemo documents that it returns nothing useful — it runs only for its side effect.
Function Overloads
TypeScript lets a single function declare multiple overload signatures — different parameter and return type combinations — followed by one implementation that handles them all. The compiler checks calls against the overload signatures, not the implementation signature, giving callers precise types per input.
Create a file named overloads.ts:
| |
The three signatures above the implementation are the public API. The final signature (with the union type) is only visible inside the function body — callers can’t use it directly, which keeps the accepted types strictly limited to string, number, or boolean.
Running with Docker
Run each example with ts-node, which compiles and executes TypeScript in a single step. No local Node.js install is required.
| |
Expected Output
Running functions.ts:
add(2, 3) = 5
Ada (member)
Linus (admin, age 35)
sum(1, 2, 3, 4) = 10
multiply(4, 5) = 20
factorial(5) = 120
applyTwice(3, double) = 12
firstElement(['a', 'b']) = a
Running closures_scope.ts:
counter() = 1
counter() = 2
counter() = 3
Hello from inside scopeDemo
Running overloads.ts:
string: typescript
number: 42
boolean: true
Key Concepts
- Type the whole signature: Parameters and return values can each carry type annotations, and the compiler checks every call site against them.
- Flexible parameters: Use
?for optional parameters,=for defaults, and...for rest parameters that gather extra arguments into a typed array. - Functions are first-class values: Store them in variables, pass them to other functions, and return them — annotate them with function types like
(x: number) => number. - Closures capture scope: An inner function retains access to the variables of the scope it was defined in, even after the outer function returns.
- Generics keep functions reusable and type-safe: A type parameter like
<T>lets one function work over many types while preserving precise types, often through inference. - Overloads express related signatures: Multiple overload signatures over one implementation give callers exact types for each kind of input.
- Inference reduces noise: TypeScript often infers return types and arrow-function parameters, so annotate where it adds clarity rather than everywhere.
Comments
Loading comments...
Leave a Comment