In Dart, when should a function be top-level, static or an instance method, and why does Effective Dart discourage static-only classes?
answer
- Dart does not require a class
- main is where it all starts
- a library is the namespace
- import prefixes, show and hide
- needs state, or makes an instance
basics
~20 sDart functions can be top-level, static or instance methods: instance for behaviour that needs an object's state, static for type-tied factories and parsers, and top-level for standalone helpers and main. Effective Dart avoids static-only classes because libraries already provide namespaces.
solid answer
~40 sDart lets functions live at the top level of a library, which is where `main(List<String> args)` must be; `args` holds only the command-line arguments after the script. I make something an **instance method** when it needs the object's state or should be overridable, a **static method** when it is the type's own way to create or look something up, like `int.parse`, and a **top-level function** for standalone helpers. Effective Dart says to avoid classes with only static members: the class is never instantiated, and a library is a better namespace because importers can use `as` prefixes or `show`/`hide` to resolve clashes themselves. Groups of related constants are the accepted exception.
code
dart · 24 lines// bin/format.dart
String formatPrice(num amount, {int decimals = 2}) =>
'${_round(amount, decimals)} EUR'; // top-level, public
String _round(num value, int decimals) =>
value.toStringAsFixed(decimals); // top-level, library-private
class Money {
Money(this.cents);
final int cents;
static Money? tryParse(String input) { // static: makes a Money
final value = double.tryParse(input);
return value == null ? null : Money((value * 100).round());
}
String format() => formatPrice(cents / 100); // instance: uses cents
}
void main(List<String> args) {
print(args); // e.g. [9.99] for: dart run bin/format.dart 9.99
final money = Money.tryParse(args.isEmpty ? '0' : args.first);
print(money?.format());
}go deeper
Recall that Dart supports top-level functions, that main is one, and that main can take a List<String> of command-line arguments.
Explain the difference between top-level, static and instance functions and the library-as-namespace argument against static-only classes.
Choose placement for testability and evolution: overridable instance methods where behaviour varies, top-level helpers where it does not.
Shape package structure so libraries, not utility classes, group related functions, with import prefixes handling collisions across teams.
## Three places a function can live Dart does not force every function into a class. A function can be: | Kind | Declared | Called as | Can use `this` | |---|---|---|---| | **Top-level** | directly in a library file | `formatPrice(9.99)` | no | | **Static method** | `static` inside a class | `Money.parse('9.99')` | no | | **Instance method** | inside a class, no `static` | `price.format()` | yes | - A **top-level function** belongs to its library. A leading underscore, `_round()`, makes it private to that library. - A **static method** belongs to the class's namespace but has no instance; it cannot touch instance fields. - An **instance method** operates on one object's state and can be overridden by subclasses. ## `main` is the required top-level function Every Dart program starts at a **top-level `main()`** function. The dart.dev guide describes it as returning `void` with an optional `List<String>` parameter for arguments: ```dart void main(List<String> arguments) { print(arguments); } ``` Running `dart run args.dart 1 test` gives `arguments == ['1', 'test']`: only the arguments after the script, as strings. A Flutter app's `main` typically takes no arguments and calls `runApp`: `void main() => runApp(const App());`. For real command-line parsing, the `args` package builds on this list. ## Why Effective Dart discourages classes of only static members Developers coming from languages where everything must live in a class often write `class StringUtils { static String pad(...) ... }`. Effective Dart says to **avoid defining a class that contains only static members**: - Dart has top-level functions, variables and constants, so the class adds nothing. - If you want a namespace, a **library** is the better tool: importers can add a prefix (`import 'format.dart' as fmt;`) or use `show` and `hide` to resolve collisions their own way. - A type that is never instantiated is a code smell; in idiomatic Dart, classes describe kinds of objects. The guide allows exceptions for groups of constants and enum-like types, and the `avoid_classes_with_only_static_members` lint (not in the default sets) can enforce the rule. ## When static is still right 1. **Factories and parsers tied to a type**: `int.parse`, `DateTime.now`, `Money.tryParse(...)` read naturally as "the class's way of making one". 2. **Constants and helpers that belong to a type's concept** alongside instance members: `Duration.zero`, `Colors`-style palettes. 3. **Private helpers used only by one class**, where a static method keeps them next to their only caller. ## When instance methods are right When the behaviour needs the object's state or should be overridable. A `PriceFormatter` configured with a currency and precision exposes `format(amount)` as an instance method, because each formatter carries its own settings. ## A decision checklist - Does the function need an object's state, or should subclasses override it? **Instance method.** - Is it a way to create or look up an instance of one type? **Static method** (or a factory constructor). - Is it a standalone helper or a program entry point? **Top-level function**, in a library whose name gives it context. ## What an interviewer listens for - That Dart supports top-level functions, and `main` is one. - What `main(List<String> args)` receives. - Effective Dart's stance on static-only classes and the library-as-namespace alternative. - A reason, not a habit, for each choice.
- What exactly does main(List<String> args) receive when you run dart run args.dart 1 test?A `List<String>` with the arguments after the script: `['1', 'test']`. Values arrive as strings, so numbers must be parsed with `int.parse` or similar. The parameter is optional; `void main()` is valid when the program takes no arguments, which is the usual shape of a Flutter app's `main`.
- How do you resolve a name clash between two Dart libraries that both export a top-level format function?Import one or both with a prefix, `import 'money.dart' as money;`, and call `money.format(...)`, or narrow imports with `show` and `hide`. That is why Effective Dart prefers libraries over static-only classes as namespaces: the importer decides how to handle collisions.
saying these in an interview costs you the question
- Thinks every Dart function must live in a class
- Wraps helpers in a Utils class of static methods by habit
- Believes main receives the script name as the first argument
- Says static methods can read instance fields
- Makes state-dependent behaviour static and passes the object in