flutter
/

Dart Functions – Complete Guide with Examples

Last Sync: Today

On this page

16
0%
5 min read
Remaining
5 minleft

Click any section to jump — progress syncs automatically

flutter

Dart Functions – Complete Guide with Examples

What are Functions?

Functions are reusable blocks of code that perform a specific task. They help you organize your code, avoid repetition, and make it more readable and maintainable. In Dart, functions are first‑class objects, meaning they can be assigned to variables, passed as arguments, and returned from other functions.

Defining a Function

A function definition typically includes a return type, a name, a parameter list (optional), and a body. If no return type is specified, void is assumed (but it's good practice to be explicit).

DARTRead-only
1
// Function that returns void (no value)
void sayHello() {
  print('Hello!');
}

// Function that returns an int
int add(int a, int b) {
  return a + b;
}

Calling a Function

DARTRead-only
1
void main() {
  sayHello();
  int result = add(5, 3);
  print('Result: $result');
}

Function Parameters

Dart supports several kinds of function parameters: required positional parameters, optional positional parameters, and named parameters.

Required Positional Parameters

These are the most common. You must provide values for them in the order they are declared.

DARTRead-only
1
void greet(String name, int age) {
  print('Hello $name, you are $age years old.');
}

void main() {
  greet('Alice', 30); // OK
  // greet(30, 'Alice'); // Error – wrong order
}

Optional Positional Parameters

Enclose a group of parameters in square brackets [] to make them optional. You can provide default values using =.

DARTRead-only
1
void log(String message, [String? level]) {
  var lvl = level ?? 'INFO';
  print('[$lvl] $message');
}

void main() {
  log('App started');           // [INFO] App started
  log('Error', 'ERROR');        // [ERROR] Error
}

Named Parameters

Named parameters allow you to pass arguments by name, making the call more readable. They are enclosed in curly braces {}. By default, named parameters are optional and can be null unless marked required or given a default value.

DARTRead-only
1
void createUser({required String name, int age = 18, String? city}) {
  print('Name: $name, Age: $age, City: $city');
}

void main() {
  createUser(name: 'Bob', age: 25);
  createUser(name: 'Alice'); // age uses default 18
}

Return Types

Functions can return a value. Specify the type before the function name. If nothing is returned, use void. You can also omit the return type, but it's not recommended.

DARTRead-only
1
int multiply(int a, int b) => a * b; // arrow syntax
String fullName(String first, String last) => '$first $last';

void main() {
  print(multiply(4, 3)); // 12
  print(fullName('John', 'Doe')); // John Doe
}

Arrow Syntax (Fat Arrow)

For functions that consist of a single expression, you can use the arrow syntax => followed by the expression. The result of the expression is automatically returned.

DARTRead-only
1
int square(int x) => x * x;

void main() => print('Square of 5: ${square(5)}');

Anonymous Functions (Lambdas)

You can create functions without a name. They are often used as arguments to other functions (like forEach, map, where).

DARTRead-only
1
void main() {
  var list = [1, 2, 3];
  
  // Anonymous function
  list.forEach((item) {
    print('Item: $item');
  });
  
  // Even shorter with arrow
  list.forEach((item) => print('Item: $item'));
}

Closures

A closure is a function that can access variables from its outer scope even after the outer function has finished executing. This is a powerful feature in Dart.

DARTRead-only
1
Function makeAdder(int addBy) {
  return (int i) => i + addBy; // closure captures addBy
}

void main() {
  var add2 = makeAdder(2);
  var add5 = makeAdder(5);
  
  print(add2(3)); // 5
  print(add5(3)); // 8
}

Lexical Scope

Dart uses lexical scoping, meaning the scope of variables is determined by the code structure. Inner functions can access variables from outer functions.

DARTRead-only
1
void outer() {
  int x = 10;
  
  void inner() {
    print(x); // can access x
  }
  inner();
}

Recursion

A function can call itself. This is useful for tasks that can be broken down into similar subtasks, like calculating factorials or traversing trees.

DARTRead-only
1
int factorial(int n) {
  if (n <= 1) return 1;
  return n * factorial(n - 1);
}

void main() {
  print(factorial(5)); // 120
}

Function as First‑Class Objects

You can assign a function to a variable, pass it as an argument, or return it from another function. This enables functional programming patterns.

DARTRead-only
1
void main() {
  // Assign function to variable
  var shout = (String s) => s.toUpperCase();
  print(shout('hello')); // HELLO
  
  // Pass function as argument
  List<int> numbers = [1, 2, 3];
  var doubled = numbers.map((n) => n * 2);
  print(doubled); // (2, 4, 6)
}

Complete Example

DARTRead-only
1
void main() {
  // Simple function
  print('Sum: ${add(10, 5)}');
  
  // Function with named parameters
  printArea(width: 5, height: 10);
  
  // Anonymous function with list
  var scores = [85, 92, 78];
  scores.forEach((score) {
    print('Score: $score');
  });
  
  // Closure
  var counter = makeCounter();
  print(counter()); // 1
  print(counter()); // 2
}

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

void printArea({required int width, required int height}) {
  print('Area: ${width * height}');
}

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

Key Takeaways

    • Functions are reusable blocks of code that may return a value.
    • Parameters can be required positional, optional positional, or named (with required or defaults).
    • Use arrow syntax => for single‑expression functions.
    • Anonymous functions (lambdas) are useful for callbacks and functional operations.
    • Closures capture variables from their enclosing scope.
    • Dart functions are first‑class objects – you can assign them, pass them, and return them.
    • Recursion is supported and can be elegant for certain problems.

Try it yourself

void main() {
  // Define a simple function
  int add(int a, int b) {
    return a + b;
  }
  
  // Call the function
  print('3 + 5 = ${add(3, 5)}');
  
  // Try an arrow function
  int multiply(int a, int b) => a * b;
  print('4 * 7 = ${multiply(4, 7)}');
}

Test Your Knowledge

Q1
of 4

What is the output of this code? void main() { var x = 5; void inner() { x = 10; } inner(); print(x); }

A
5
B
10
C
null
D
Error
Q2
of 4

Which parameter type must be explicitly marked `required`?

A
Positional parameters
B
Optional positional parameters
C
Named parameters
D
All parameters
Q3
of 4

What does the following code print? void main() => print('Hello');

A
Hello
B
void
C
Error
D
Nothing
Q4
of 4

What is a closure?

A
A function that has no name
B
A function that can access variables from its outer scope even after the outer function has finished
C
A function that returns another function
D
A function that is defined inside a class

Frequently Asked Questions

What is the difference between positional and named parameters?

Positional parameters are identified by their order in the function call; you must provide them in the correct sequence. Named parameters are identified by their name and can be provided in any order. Named parameters are enclosed in {} and are optional by default unless marked required.

Can a function have both optional positional and named parameters?

No, a function cannot mix optional positional parameters and named parameters. You can have either optional positional parameters (using []) or named parameters (using {}), but not both. Required positional parameters can coexist with named parameters.

What is the difference between arrow syntax (`=>`) and a regular function body?

Arrow syntax is a shorthand for functions that contain a single expression. The expression is automatically returned. Regular function bodies (with {}) can contain multiple statements and require an explicit return statement (unless the return type is void).

How do closures work in Dart?

A closure is a function that captures variables from the scope in which it was defined. Even after that scope exits (e.g., the outer function returns), the closure retains access to those variables. This enables powerful patterns like function factories and data hiding.

Can a function be assigned to a variable?

Yes, because functions are first‑class objects in Dart. You can assign a function to a variable, pass it as an argument, or return it from another function. For example: var myFunc = (int x) => x * 2;

What is an anonymous function (lambda) and when should I use it?

An anonymous function is a function without a name. It's commonly used as a short‑lived callback, especially with collection methods like forEach, map, where, or event handlers. They are defined inline at the point of use.

What is recursion and when is it useful?

Recursion is when a function calls itself. It's useful for problems that can be broken down into smaller, similar sub‑problems, such as tree traversal, combinatorial algorithms, and mathematical functions like factorial or Fibonacci. However, deep recursion can lead to stack overflow; consider iterative solutions for very deep recursion.

What is the `void` keyword?

void indicates that a function does not return a value. If you omit the return type, Dart assumes dynamic (which is not recommended). Always specify void for functions that only perform side effects.

Previous

dart loops

Next

dart null safety

Related Content

Need help?

Explore our comprehensive docs or start a chat with our tech experts.