| |

Dart 12 🎯 Functions: Named, Optional, Default Parameters, and Arrow Syntax (=>)

Functions are the building blocks of Dart programs. They package a sequence of statements behind a name, accept inputs as parameters, produce an output as a return value, and can be called from anywhere that has access to them. Dart’s function syntax follows the C-family convention with a return type, a name, a parameter list, and a body, but the parameter system is more expressive than most languages: parameters can be required or optional, positional or named, and can carry default values. The arrow syntax => provides a concise form for functions whose body is a single expression.

What distinguishes Dart’s functions is the combination of flexibility and safety. Optional parameters must be declared as such, and the compiler checks that callers provide all required arguments. Named parameters make call sites readable by labeling each argument with the parameter it fills. Default values let a parameter be omitted without losing type safety. The arrow syntax reduces ceremony for simple functions without changing their semantics.

This chapter covers function definitions, the four parameter kinds (required positional, optional positional, required named, optional named), default values, the arrow syntax, first-class functions, closures, and the patterns that make functions readable and reusable.

Key point: Dart functions have a return type, a name, and a parameter list. Parameters can be required positional, optional positional ([]), required named (required), or optional named ({}). Default values are written after =. The arrow syntax => is shorthand for a body that is a single expression. Functions are first-class values and can be assigned, passed, and returned.


Why Dart’s function parameters are expressive

The readability problem. A function call like createUser('Alice', 30, true, false) gives no indication of what each argument means. Named parameters fix this: createUser(name: 'Alice', age: 30, isAdmin: true, isActive: false). The call site documents itself.

The optionality problem. Some parameters are required, and some are optional. Without a syntax for optionality, a function must either provide every argument or use sentinel values like null or -1. Dart’s optional parameters make the distinction explicit and let the compiler enforce it.

The default value problem. An optional parameter without a default is null, which forces the caller to handle null. A default value gives the parameter a meaningful value when omitted, and the type stays non-nullable.

The ceremony problem. A function whose body is a single expression does not need braces and a return. The arrow syntax => makes the function a single line, which reads like a mathematical definition.

The higher-order problem. Functions are values in Dart. They can be stored in variables, passed as arguments, returned from other functions, and composed. This enables callbacks, strategies, and the functional collection methods that Dart uses heavily.


a. Basic function definitions

A function has a return type, a name, a parameter list in parentheses, and a body in braces.

int add(int a, int b) {
  return a + b;
}

The return type is required unless the function returns void or the type can be inferred. When the return type is void, it can be omitted, but writing it explicitly is clearer.

void greet(String name) {
  print('Hello, $name!');
}

A function with no parameters takes empty parentheses:

void sayHello() {
  print('Hello!');
}

A function with no return value can omit void, but the explicit form is preferred for clarity. The compiler infers void when nothing is returned and the return type is not annotated.


b. Required positional parameters

Required positional parameters are listed in order and must be provided by the caller.

int add(int a, int b) {
  return a + b;
}

add(2, 3);  // 5

The caller must provide all required positional parameters, in order. Omitting one or providing the wrong type is a compile error.

add(2);        // Error: too few arguments
add(2, 3, 4);  // Error: too many arguments
add('a', 'b'); // Error: wrong type

Positional parameters are the default. They are appropriate when the order is obvious and the number of parameters is small.


c. Optional positional parameters

Optional positional parameters are wrapped in square brackets. They may be omitted by the caller, and they default to null if no default is provided.

String greet(String name, [String? greeting]) {
  return '${greeting ?? 'Hello'}, $name!';
}

greet('Alice');              // "Hello, Alice!"
greet('Alice', 'Hi');        // "Hi, Alice!"

The optional parameters must come after the required ones. A function cannot have a required parameter after an optional one.

// Valid
void f(int a, [int? b, int? c]) {}

// Invalid: required after optional
void f(int a, [int? b], int c) {}  // Error

Optional positional parameters are appropriate when the parameters form a natural sequence and the extras are genuinely optional. They are less common than named parameters because the call site does not label the arguments.


d. Named parameters

Named parameters are wrapped in curly braces. They are labeled at the call site, which makes the call readable.

void createUser({String? name, int? age}) {
  print('Name: $name, Age: $age');
}

createUser(name: 'Alice', age: 30);
createUser(age: 30, name: 'Alice');  // order does not matter
createUser(name: 'Alice');           // age omitted

Named parameters are optional by default, which means the function must handle the case where they are null. To make a named parameter required, prefix it with required.

void createUser({required String name, required int age, bool isAdmin = false}) {
  print('$name, $age, admin: $isAdmin');
}

createUser(name: 'Alice', age: 30);  // isAdmin defaults to false

The required keyword makes the parameter mandatory while still allowing it to be named. This is the standard way to write functions with more than two or three parameters, because the call site becomes self-documenting.

A function can mix positional and named parameters. Positional parameters come first, named parameters after:

void order(String item, {int quantity = 1, String? note}) {
  print('$quantity x $item${note != null ? ' ($note)' : ''}');
}

order('Coffee');                         // 1 x Coffee
order('Coffee', quantity: 3);            // 3 x Coffee
order('Coffee', quantity: 3, note: 'hot'); // 3 x Coffee (hot)

The positional parameter item is required and comes first. The named parameters quantity and note are optional and labeled.


e. Default values

A default value is written after the parameter name and an equals sign. It applies when the caller omits the parameter.

void greet(String name, {String greeting = 'Hello'}) {
  print('$greeting, $name!');
}

greet('Alice');              // Hello, Alice!
greet('Alice', greeting: 'Hi');  // Hi, Alice!

Default values must be compile-time constants. They can be literals, const values, or expressions that are constant.

void f({int count = 0, String name = 'Guest', List<int> items = const []}) {}

A default value can be null if the parameter is nullable, but this is the same as omitting the default, since optional parameters are null by default.

void f({String? name = null}) {}  // same as {String? name}

Default values are used in the body only when the caller does not provide the argument. They are not used when the caller explicitly passes null.

greet('Alice', greeting: null);  // Error if greeting is non-nullable

If the parameter is nullable and the caller passes null, the default does not apply. The body receives null.


f. The arrow syntax

The arrow syntax => is shorthand for a function whose body is a single expression. It returns the value of that expression.

int add(int a, int b) => a + b;

String greet(String name) => 'Hello, $name!';

bool isEven(int n) => n % 2 == 0;

The arrow form is equivalent to a body with a single return. It does not change the function’s behavior; it only removes the braces and the return keyword.

// These are equivalent
int add(int a, int b) => a + b;

int add(int a, int b) {
  return a + b;
}

The arrow form is appropriate when the body is a single expression. When the body contains multiple statements, conditionals, or loops, the block form is required.

// Not valid: multiple statements
int f(int a) => { print(a); return a; };  // Error

// Valid: single expression
int f(int a) => a * 2;

An arrow function can contain a conditional expression:

String describe(int n) => n > 0 ? 'positive' : n < 0 ? 'negative' : 'zero';

It cannot contain an if statement, because a statement is not an expression. The ternary is the expression form of a conditional.


g. First-class functions and closures

Functions are values in Dart. They can be assigned to variables, passed as arguments, and returned from other functions.

int add(int a, int b) => a + b;

var operation = add;
print(operation(2, 3));  // 5

A function type is written with the parameter types and the return type:

int Function(int, int) operation = add;

Functions can be passed as arguments:

void apply(int a, int b, int Function(int, int) op) {
  print(op(a, b));
}

apply(2, 3, add);          // 5
apply(2, 3, (a, b) => a * b);  // 6

An anonymous function (also called a lambda or closure) is a function without a name:

var square = (int x) => x * x;
print(square(5));  // 25

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

Function makeCounter() {
  var count = 0;
  return () {
    count++;
    return count;
  };
}

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

The returned function captures count, and each call increments it. The variable persists because the closure holds a reference to it.

Closures are the basis for callbacks, event handlers, and the functional collection methods:

var numbers = [1, 2, 3, 4, 5];
var evens = numbers.where((n) => n % 2 == 0).toList();
var doubled = numbers.map((n) => n * 2).toList();

The arrow syntax is commonly used with these methods because the callbacks are single expressions.


Complete Example Session

// ============================================
// PART 1: BASIC FUNCTION
// ============================================
int add(int a, int b) {
  return a + b;
}

void main() {
  print(add(2, 3));  // 5
}
// ============================================
// PART 2: ARROW SYNTAX
// ============================================
int add(int a, int b) => a + b;
bool isEven(int n) => n % 2 == 0;

void main() {
  print(add(2, 3));      // 5
  print(isEven(4));      // true
}
// ============================================
// PART 3: OPTIONAL POSITIONAL
// ============================================
String greet(String name, [String? greeting]) {
  return '${greeting ?? 'Hello'}, $name!';
}

void main() {
  print(greet('Alice'));         // Hello, Alice!
  print(greet('Alice', 'Hi'));   // Hi, Alice!
}
// ============================================
// PART 4: NAMED PARAMETERS
// ============================================
void createUser({String? name, int? age}) {
  print('Name: $name, Age: $age');
}

void main() {
  createUser(name: 'Alice', age: 30);
  createUser(age: 30, name: 'Alice');  // order does not matter
  createUser(name: 'Alice');
}
// ============================================
// PART 5: REQUIRED NAMED
// ============================================
void createUser({
  required String name,
  required int age,
  bool isAdmin = false,
}) {
  print('$name, $age, admin: $isAdmin');
}

void main() {
  createUser(name: 'Alice', age: 30);
}
// ============================================
// PART 6: DEFAULT VALUES
// ============================================
void greet(String name, {String greeting = 'Hello'}) {
  print('$greeting, $name!');
}

void main() {
  greet('Alice');                    // Hello, Alice!
  greet('Alice', greeting: 'Hi');    // Hi, Alice!
}
// ============================================
// PART 7: MIXING POSITIONAL AND NAMED
// ============================================
void order(String item, {int quantity = 1, String? note}) {
  print('$quantity x $item${note != null ? ' ($note)' : ''}');
}

void main() {
  order('Coffee');
  order('Coffee', quantity: 3);
  order('Coffee', quantity: 3, note: 'hot');
}
// ============================================
// PART 8: FUNCTIONS AS VALUES
// ============================================
int add(int a, int b) => a + b;

void main() {
  var operation = add;
  print(operation(2, 3));  // 5

  int Function(int, int) multiply = (a, b) => a * b;
  print(multiply(2, 3));   // 6
}
// ============================================
// PART 9: PASSING FUNCTIONS AS ARGUMENTS
// ============================================
void apply(int a, int b, int Function(int, int) op) {
  print(op(a, b));
}

void main() {
  apply(2, 3, (a, b) => a + b);  // 5
  apply(2, 3, (a, b) => a * b);  // 6
}
// ============================================
// PART 10: CLOSURES
// ============================================
Function makeCounter() {
  var count = 0;
  return () {
    count++;
    return count;
  };
}

void main() {
  var counter = makeCounter();
  print(counter());  // 1
  print(counter());  // 2
  print(counter());  // 3
}

These ten parts cover basic functions, arrow syntax, optional positional parameters, named parameters, required named parameters, default values, mixing positional and named, functions as values, passing functions as arguments, and closures.


Quick Reference

Parameter Types

TypeSyntaxRequiredOrdered
Required positional(int a)YesYes
Optional positional([int? a])NoYes
Required named({required int a})YesNo
Optional named({int? a})NoNo

Default Values

FormExample
Named with default{String greeting = 'Hello'}
Positional with default([String greeting = 'Hello'])
Required with default({required String name, bool flag = false})
Constant default{List<int> items = const []}

Arrow Syntax

FormExample
Expressionint add(int a, int b) => a + b;
TernaryString f(int n) => n > 0 ? 'pos' : 'neg';
Method callint len(String s) => s.length;
ConstructorPoint(this.x, this.y);

Function Types

TypeMeaning
int Function(int, int)Takes two ints, returns int
void Function()Takes nothing, returns void
String Function(String)Takes a string, returns a string
FunctionAny function

Function Forms

FormExample
Namedint add(int a, int b) => a + b;
Anonymous(int x) => x * x
Closure() { count++; return count; }
Assignedvar f = (int x) => x * 2;

Best Practices

✅ Do This:

// Use named parameters for clarity
void createUser({required String name, required int age}) {}

// Provide defaults for optional parameters
void greet(String name, {String greeting = 'Hello'}) {}

// Use arrow syntax for single expressions
int add(int a, int b) => a + b;

// Mark required named parameters explicitly
void send({required String to, required String body}) {}

// Use closures for stateful callbacks
var counter = makeCounter();

❌ Don’t Do This:

// Too many positional parameters
void createUser(String name, int age, bool admin, bool active) {}  // ❌ use named

// Non-constant default
void f({int x = someFunction()}) {}  // ❌ must be constant

// Required parameter after optional
void f(int a, [int? b], int c) {}  // ❌ syntax error

// Arrow with statements
int f(int a) => { print(a); return a; };  // ❌ not an expression

// Mutating captured variables unexpectedly
for (var i = 0; i < 3; i++) {
  callbacks.add(() => print(i));  // ❌ captures i by reference
}

Common Pitfalls

PitfallWhy It HappensFix
Required after optionalPositional orderReorder: required first
Default not appliedCaller passed nullDefault applies only when omitted
Non-constant defaultUsed a function callUse a literal or const
Arrow with statementsMultiple statementsUse block body
Closure captures loop variableShared referenceUse a local copy inside the loop
Named parameter misspelledCompiler errorCheck the parameter name

Real-World Examples

1. Simple Arrow Function

int square(int x) => x * x;

2. Function with Default

void greet(String name, {String greeting = 'Hello'}) {
  print('$greeting, $name!');
}

3. Required Named Parameters

void createUser({required String name, required int age}) {}

4. Optional Positional

String join(String a, [String separator = ',']) => '$a$separator';

5. Function as Parameter

void forEach(List<int> list, void Function(int) action) {
  for (var item in list) action(item);
}

6. Closure Counter

Function makeCounter() {
  var count = 0;
  return () => ++count;
}

7. Function Type Variable

int Function(int, int) op = (a, b) => a + b;

8. Method Call in Arrow

int length(String s) => s.length;

9. Ternary in Arrow

String sign(int n) => n > 0 ? 'positive' : 'negative';

10. Higher-Order Function

List<int> transform(List<int> list, int Function(int) fn) {
  return list.map(fn).toList();
}

Visual

Parameter Kinds

┌──────────────────────────────────────────────────────────────┐
│  REQUIRED POSITIONAL:                                        │
│  void f(int a, int b)                                        │
│  └── Caller must provide both, in order                      │
│                                                              │
│  OPTIONAL POSITIONAL:                                        │
│  void f(int a, [int? b])                                     │
│  └── b may be omitted                                        │
│                                                              │
│  REQUIRED NAMED:                                             │
│  void f({required int a, required int b})                    │
│  └── Caller must provide both, labeled                       │
│                                                              │
│  OPTIONAL NAMED:                                             │
│  void f({int? a, int b = 0})                                 │
│  └── Caller may omit, labeled                                │
└──────────────────────────────────────────────────────────────┘

Arrow Syntax

┌──────────────────────────────────────────────────────────────┐
│  int add(int a, int b) => a + b;                             │
│                                                              │
│  Equivalent to:                                              │
│  int add(int a, int b) {                                     │
│    return a + b;                                             │
│  }                                                           │
│                                                              │
│  The arrow form is for single expressions.                   │
│  For multiple statements, use braces.                        │
└──────────────────────────────────────────────────────────────┘

Named Parameters at Call Site

┌──────────────────────────────────────────────────────────────┐
│  void createUser({required String name, required int age}) {}│
│                                                              │
│  createUser(name: 'Alice', age: 30);                         │
│  └── Self-documenting                                        │
│                                                              │
│  Versus positional:                                          │
│  createUser('Alice', 30);                                    │
│  └── What does 30 mean?                                      │
└──────────────────────────────────────────────────────────────┘

Closures

┌──────────────────────────────────────────────────────────────┐
│  Function makeCounter() {                                    │
│    var count = 0;                                            │
│    return () {                                               │
│      count++;                                                │
│      return count;                                           │
│    };                                                        │
│  }                                                           │
│                                                              │
│  var counter = makeCounter();                                │
│  counter();  // 1                                             │
│  counter();  // 2                                             │
│  counter();  // 3                                             │
│                                                              │
│  The closure captures 'count' and persists it between calls. │
└──────────────────────────────────────────────────────────────┘

Summary

ItemValue
Basic functionint add(int a, int b) { return a + b; }
Arrow functionint add(int a, int b) => a + b;
Required positional(int a, int b)
Optional positional([int? a, int? b])
Required named({required int a, required int b})
Optional named({int a = 0})
Default value{String greeting = 'Hello'}
Default must beCompile-time constant
Function typeint Function(int, int)
Anonymous function(int x) => x * x
ClosureCaptures variables from enclosing scope

Key takeaways:

  • Dart functions have a return type, a name, and a parameter list. The return type can be omitted for void, but explicit types are preferred for clarity.
  • Positional parameters are the default; named parameters are for readability. Named parameters make the call site self-documenting, which matters when a function has more than two or three parameters.
  • Required named parameters use the required keyword. Named parameters are optional by default, so required makes them mandatory while keeping the label at the call site.
  • Default values must be compile-time constants. They apply when the caller omits the parameter, not when the caller passes null.
  • Optional parameters must come after required ones. A required positional parameter cannot follow an optional positional one.
  • The arrow syntax is for single expressions. => is equivalent to a body with a single return. It cannot contain statements, so multiple statements require the block form.
  • Functions are first-class values. They can be assigned to variables, passed as arguments, returned from functions, and captured in closures. This enables callbacks and the functional collection methods.
  • Closures capture variables by reference. The captured variable persists as long as the closure exists. In a loop, this can produce surprising results if the loop variable is captured directly.

Remember: Dart’s function syntax is more expressive than most languages because it distinguishes required from optional and positional from named. Named parameters make call sites readable; defaults let optional parameters have meaningful values; required keeps mandatory parameters mandatory while allowing labels. The arrow syntax reduces ceremony for single-expression functions without changing their semantics. Functions are values, so they can be composed, passed, and returned, which is the foundation of Dart’s functional collection methods. Closures capture variables from their enclosing scope, which enables stateful callbacks. Understanding these features means understanding how Dart code is organized: small, well-typed functions with clear parameters, composed into larger operations through higher-order functions.



Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!