Laptop on a desk showing colorful program code on screen

Dart Functions for Beginners: Parameters Made Simple

You have been writing all your code inside main(), and it works, but you keep copying the same three lines to a new spot and tweaking one value. That copy-paste itch is exactly what Dart functions cure. A function is a named chunk of work you can run whenever you want, as many times as you want, by calling its name. You already call them constantly without thinking about it: print() is a function, and so is trim(). Now you get to write your own. By the end of this post you will be able to declare a function, hand it values through parameters, get an answer back with return, and use Dart’s three flavors of parameters (positional, named, and optional) to make your calls read clearly instead of turning into a guessing game.

What a Dart function actually is

A function has four parts, and once you see them named you will spot them everywhere. There is a return type (what kind of value it hands back), a name, a pair of parentheses for parameters, and a body in curly braces. Here is the smallest useful one.

void greet() {
  print('Hello from a function!');
}

void main() {
  greet(); // Hello from a function!
  greet(); // Hello from a function!
}

Run that and you see the greeting twice, once for each time you called greet(). Write the work once, run it as often as you like. That is the whole point.

The word void at the front is the return type, and it means this function hands nothing back. It does its job (printing) and that is it. Plenty of functions are like that. But the ones you will lean on most give you a value in return, and for that you need return.

int daysInWeeks(int weeks) {
  return weeks * 7;
}

void main() {
  int days = daysInWeeks(3);
  print(days); // 21
}

Now the return type is int, because this function hands back a whole number. The return keyword does two things at once: it sends the value out, and it stops the function right there. Anything after a return that runs never gets reached. You catch the returned value in a variable, or use it directly, the same way you use the result of any expression.

Positional parameters and the arrow shortcut

The values in the parentheses are parameters, and the plain kind are called positional because their order is what matters. Here is a function that takes two of them.

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

void main() {
  print(add(2, 3));  // 5
  print(add(10, 7)); // 17
}

When you call add(2, 3), the 2 lands in a and the 3 lands in b, strictly by position. Swap them to add(3, 2) and for addition you get the same answer, but for subtraction that order would flip your result completely. This is the thing to watch with positional parameters: the caller has to remember what goes where, and nothing on the calling line reminds them.

A lot of small functions are just one line that returns something, like add above. Dart gives you a shorthand for exactly that shape, the arrow. When a function body is a single expression, you can drop the braces and the return and write => instead.

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

String shout(String word) => word.toUpperCase();

void main() {
  print(add(2, 3));      // 5
  print(shout('dart'));  // DART
}

The arrow version of add does the same job as the four-line version, just tighter. Read => as “returns.” Use it for one-liners, and stick with braces the moment a function needs more than one line, because cramming logic behind an arrow gets hard to read fast.

Named parameters, defaults, and required

Positional parameters get awkward the second you have three or four of them. A call like createUser('Ada', 36, true, false) tells you nothing about what those last three values mean. Named parameters fix that by putting the parameter’s name right there in the call. You mark them by wrapping the parameters in curly braces when you declare the function.

String describe(String name, {int age = 0, String city = 'unknown'}) {
  return '$name is $age, from $city';
}

void main() {
  print(describe('Ada'));                          // Ada is 0, from unknown
  print(describe('Ada', age: 36));                 // Ada is 36, from unknown
  print(describe('Ada', city: 'London', age: 36)); // Ada is 36, from London
}

Two things are worth slowing down on here. First, named parameters are optional by default, which is why describe('Ada') works with nothing but a name. The = 0 and = 'unknown' are default values, and they fill in whenever the caller leaves that parameter out. Second, because each value is labeled, order stops mattering: that last call passes city before age and Dart sorts it out. Compare the readability of describe('Ada', city: 'London', age: 36) against four bare positional values and you can feel why Flutter’s widgets are built almost entirely on named parameters.

Sometimes a named parameter should not be skippable. If a function genuinely cannot do its job without a value, mark it required, and Dart will refuse to compile a call that leaves it out. This keyword comes from null safety, so you need Dart 2.12 or newer, which any current Flutter install already gives you.

String label({required String text, int size = 14}) {
  return '$text ($size px)';
}

void main() {
  print(label(text: 'Submit'));           // Submit (14 px)
  print(label(text: 'Cancel', size: 20)); // Cancel (20 px)
}

Here text must always be supplied, while size stays optional with a sensible default. That mix, one required piece plus a few tweakable options, is the pattern you will write over and over.

Modern workspace with a laptop displaying code and a notebook
A tidy workspace for writing and testing Dart functions.

Optional positional parameters

There is a middle option between “always positional” and “named.” Sometimes you want a positional parameter that the caller can simply leave off. You get that by wrapping it in square brackets, and because it might not be supplied, its value can be missing, so you make its type nullable with a question mark.

String fullName(String first, [String? last]) {
  if (last == null) return first;
  return '$first $last';
}

void main() {
  print(fullName('Ada'));             // Ada
  print(fullName('Ada', 'Lovelace')); // Ada Lovelace
}

Call it with one argument and last comes in as null, so the function returns just the first name. Call it with two and you get both. That String? with the question mark is Dart telling you the value might be absent, and the if (last == null) check is you handling that case before you try to use it. If missing values feel fuzzy, the Dart null safety post walks through the question mark and the null checks in full.

Common mistakes and where Dart functions show up in Flutter

A few slip-ups trip up almost every first-semester student, and I see them every term. The first is forgetting to return. If you declare a return type like int but never hit a return, Dart stops you with an error along the lines of The body might complete normally, causing 'null' to be returned, but the return type is a non-nullable type. Read that as “you promised an int and did not deliver one.” The fix is a return on every path out of the function.

The second is mixing up how you pass a named parameter. Once a parameter is named, you must call it with its label. Try to sneak it in by position and Dart complains.

// describe('Ada', 36);
// Error: Too many positional arguments: 1 allowed, but 2 found.
// You have to write describe('Ada', age: 36) instead.

The error is blunt but honest: describe only accepts one positional argument (the name), so the bare 36 has nowhere to go. Add the age: label and it slots right in.

Once these click, you will notice functions everywhere in Flutter, because a Flutter app is mostly functions calling functions. Every widget you build is a constructor taking named parameters. Every onPressed you hand a button is a function. When you pull items out of a list and turn each one into a widget, you are handing a function to map, which is covered in the Dart list methods post. And the moment your function has to wait on a network call or a file, it becomes an async function, which the Dart async await post picks up from here.

Open notebook and pen on a desk for practicing Dart functions
Practice a function or two and the parameter rules stick.

Your next step

Here is the short version to keep. A Dart function is a named, reusable block: it has a return type, a name, parameters in parentheses, and a body. Use return to hand a value back, and the => arrow when the body is a single expression. Reach for positional parameters when there are only one or two and their order is obvious, named parameters (with defaults and required) when a call needs to explain itself, and optional positional parameters when a trailing value can politely be left off. That covers the vast majority of the functions you will ever write.

The way to make this stick is to write a few. Turn a two-line calculation you have lying around into a function that returns the result. Rewrite it as an arrow. Give it a named parameter with a default, then call it once with the default and once without. Add a required parameter and watch the compiler stop you when you forget it. When you want the official reference, the Dart language guide on functions lays out every variation. Open your editor, write one small function, and call it twice. That is the whole idea, and everything else in Dart builds on it.

Leave a Comment

Your email address will not be published. Required fields are marked *