Skip to main content

Functions


Basic Functions

// Standard function declaration
int add(int a, int b) {
return a + b;
}

// Void function (no return value)
void greet(String name) {
print('Hello, $name!');
}

// Calling functions
print(add(2, 3)); // 5
greet('Dart'); // Hello, Dart!

Arrow Functions (Expression Body)

When the body is a single expression, use =>:

// Arrow syntax — implicit return
int add(int a, int b) => a + b;
void greet(String name) => print('Hello, $name!');
bool isEven(int n) => n % 2 == 0;
String shout(String s) => s.toUpperCase() + '!';

// Works great with lambdas too
var square = (int x) => x * x;
print(square(5)); // 25

Optional Parameters

Positional Optional Parameters []

// Optional positional — use []
String greet(String name, [String greeting = 'Hello']) {
return '$greeting, $name!';
}

print(greet('Alice')); // Hello, Alice!
print(greet('Bob', 'Hi')); // Hi, Bob!

// Multiple optional params
void log(String msg, [String level = 'INFO', bool timestamp = false]) {
var time = timestamp ? '[${DateTime.now()}] ' : '';
print('$time[$level] $msg');
}

log('App started'); // [INFO] App started
log('Warning!', 'WARN'); // [WARN] Warning!
log('Error!', 'ERROR', true); // [timestamp] [ERROR] Error!

Named Parameters {}

// Named parameters — use {}
// Required by default? No — they're optional unless marked required
void createUser({
required String name, // ← required keyword makes it mandatory
int age = 0,
String? email, // nullable = truly optional
}) {
print('Name: $name, Age: $age, Email: ${email ?? 'N/A'}');
}

// Call with names (order doesn't matter!)
createUser(name: 'Alice');
createUser(name: 'Bob', age: 25);
createUser(name: 'Carol', age: 30, email: 'carol@example.com');

// This is how Flutter works!
Container(
width: 100,
height: 100,
color: Colors.blue,
child: Text('Hi'),
)

Default Parameter Values

// Simple defaults
void connect({
String host = 'localhost',
int port = 8080,
bool secure = false,
}) {
print('${secure ? 'https' : 'http'}://$host:$port');
}

connect(); // http://localhost:8080
connect(port: 443, secure: true); // https://localhost:443

First-Class Functions

In Dart, functions are objects and can be:

  • Assigned to variables
  • Passed as arguments
  • Returned from other functions
// Assign to variable
var sayHi = (String name) => 'Hi, $name!';
print(sayHi('Dart')); // Hi, Dart!

// Pass as argument
void applyTwice(int Function(int) fn, int value) {
print(fn(fn(value)));
}
applyTwice((x) => x * 2, 3); // 12 (3 * 2 = 6, 6 * 2 = 12)

// Return a function
Function makeAdder(int n) => (int x) => x + n;
var add5 = makeAdder(5);
print(add5(3)); // 8
print(add5(10)); // 15

// Store functions in collections
var ops = <String, int Function(int, int)>{
'add': (a, b) => a + b,
'sub': (a, b) => a - b,
'mul': (a, b) => a * b,
};
print(ops['add']!(3, 4)); // 7

Anonymous Functions (Lambdas)

// Anonymous function with block body
var multiply = (int a, int b) {
return a * b;
};

// Anonymous function with arrow body
var divide = (double a, double b) => a / b;

// Used inline — very common with collections
var numbers = [1, 2, 3, 4, 5];

// forEach
numbers.forEach((n) => print(n));

// map — transform each element
var doubled = numbers.map((n) => n * 2).toList();
print(doubled); // [2, 4, 6, 8, 10]

// where — filter
var evens = numbers.where((n) => n.isEven).toList();
print(evens); // [2, 4]

// reduce — combine
var sum = numbers.reduce((acc, n) => acc + n);
print(sum); // 15

Closures

A closure is a function that captures variables from its enclosing scope:

// Counter closure
Function makeCounter() {
int count = 0; // captured in closure
return () {
count++;
return count;
};
}

var counter = makeCounter();
print(counter()); // 1
print(counter()); // 2
print(counter()); // 3

var counter2 = makeCounter(); // independent counter
print(counter2()); // 1

// Closures capture the variable itself (not a copy)
var callbacks = <void Function()>[];
for (var i = 0; i < 3; i++) {
var captured = i; // capture current value
callbacks.add(() => print(captured));
}
callbacks[0](); // 0
callbacks[1](); // 1
callbacks[2](); // 2

Higher-Order Functions

Functions that take functions or return functions:

// Common HOF patterns
List<T> filter<T>(List<T> list, bool Function(T) predicate) {
return list.where(predicate).toList();
}

List<R> transform<T, R>(List<T> list, R Function(T) mapper) {
return list.map(mapper).toList();
}

T fold<T>(List<T> list, T initial, T Function(T acc, T item) combiner) {
return list.fold(initial, combiner);
}

void main() {
var nums = [1, 2, 3, 4, 5, 6];

var odds = filter(nums, (n) => n.isOdd);
print(odds); // [1, 3, 5]

var strings = transform(nums, (n) => 'item_$n');
print(strings); // [item_1, item_2, ...]

var total = fold(nums, 0, (acc, n) => acc + n);
print(total); // 21
}

Function Types

// Explicit function type declaration
int Function(int, int) adder = (a, b) => a + b;

// As a parameter type
void process(List<int> data, int Function(int) transform) {
for (var item in data) {
print(transform(item));
}
}

// As a return type
String Function(String) makeUpperCaser() => (s) => s.toUpperCase();

// Nullable function type
void Function()? onPressed;
onPressed?.call(); // safe call

// typedef for readability
typedef Predicate<T> = bool Function(T value);
typedef Transformer<T, R> = R Function(T input);
typedef VoidCallback = void Function();

Predicate<int> isPositive = (n) => n > 0;
VoidCallback doNothing = () {};

Recursive Functions

// Factorial
int factorial(int n) => n <= 1 ? 1 : n * factorial(n - 1);
print(factorial(5)); // 120

// Fibonacci
int fib(int n) => n <= 1 ? n : fib(n - 1) + fib(n - 2);
print(fib(10)); // 55

// Memoized Fibonacci (efficient)
final _fibCache = <int, int>{};
int fibMemo(int n) {
if (n <= 1) return n;
return _fibCache[n] ??= fibMemo(n - 1) + fibMemo(n - 2);
}

Generator Functions

Synchronous Generators sync*

// sync* returns an Iterable lazily
Iterable<int> range(int start, int end) sync* {
for (var i = start; i < end; i++) {
yield i; // produce one value at a time
}
}

for (var n in range(0, 5)) {
print(n); // 0, 1, 2, 3, 4
}

// Fibonacci sequence as infinite generator
Iterable<int> fibonacci() sync* {
var a = 0, b = 1;
while (true) {
yield a;
var temp = a + b;
a = b;
b = temp;
}
}

// Take first 10 Fibonacci numbers
fibonacci().take(10).forEach(print);
// 0, 1, 1, 2, 3, 5, 8, 13, 21, 34

// yield* — delegate to another iterable
Iterable<int> combined() sync* {
yield* range(0, 3); // yields 0, 1, 2
yield* range(10, 13); // yields 10, 11, 12
}

Asynchronous Generators async*

// async* returns a Stream
Stream<int> countDown(int from) async* {
for (var i = from; i >= 0; i--) {
await Future.delayed(Duration(seconds: 1));
yield i;
}
}

// Use await for to listen
await for (var n in countDown(5)) {
print(n); // 5, 4, 3, 2, 1, 0 (one per second)
}

main() Function

// Basic
void main() {
print('Hello!');
}

// With command-line arguments
void main(List<String> args) {
if (args.isEmpty) {
print('No arguments provided');
} else {
print('Arguments: $args');
}
}
// dart run app.dart hello world
// → Arguments: [hello, world]

// Async main
Future<void> main() async {
var result = await fetchData();
print(result);
}

Summary

FeatureSyntax
Regular functionReturnType name(params) { ... }
Arrow functionReturnType name(params) => expr;
Named paramsvoid fn({required String a, int b = 0})
Optional positionalvoid fn(String a, [int b = 0])
Anonymous(params) => expr or (params) { ... }
Function typeReturnType Function(ParamTypes)
Sync generatorIterable<T> fn() sync* { yield value; }
Async generatorStream<T> fn() async* { yield value; }