Irbisa · Cheatsheet

OOP in Dart

Classes, inheritance, interfaces, mixins, and abstract classes

Junior Developer · 22 questions

  1. 01

    What are the four OOP principles, and how does each one look in Dart?

    Encapsulation hides state behind a public API, inheritance reuses a parent's code, polymorphism lets one call run different implementations, and abstraction exposes what an object does, not how.

  2. 02

    How do constructors work in Dart (generative, named and initializer lists), and why must final fields be set before the body?

    A generative constructor creates a new instance; a named one like Point.origin() is an extra, descriptively named way to build the same class.

    class Point {  final double x;  final double y;   // generative: fields set straight from parameters  Point(this.x, this.y);…
  3. 03

    What is a factory constructor, how does it differ from a generative one, and can it return null or a cached instance?

    A factory constructor does not have to create a new object: its body decides what to return (a cached instance, a singleton or a subtype).

    class Logger {  Logger._internal(this.name);  final String name;   static final _cache = <String, Logger>{};…
  4. 04

    What does super.key in a widget constructor do, and when can you not use super parameters?

    super.key is a super parameter: the constructor takes key and forwards it to the superclass constructor.

    class Avatar extends StatelessWidget {  // before: const Avatar({Key? key, required this.url}) : super(key: key);  const Avatar({super.key, required this.url, this.size = 40});   final String url;  final double size;…
  5. 05

    Does Dart support method overloading, and how do you get the same effect?

    No: a class cannot have two methods or constructors with the same name and different parameters.

    class Toast {  // one method instead of show(text), show(text, duration), show(text, duration, action)  static void show(    String text, {    Duration duration = const Duration(seconds: 2),    String? actionLabel,…
  6. 06

    Dart has no private keyword. How does _ privacy work, and what does a ._() constructor mean?

    A name starting with _ is private to its library (usually the file), not to the class: code in the same file can still use it, other files cannot.

    // cart.dartclass Cart {  Cart._(); // private: other files cannot call it  static final instance = Cart._();   final _items = <String>[]; // private to cart.dart…
  7. 07

    How do getters and setters work in Dart, and why can a public field become a getter without breaking callers?

    Every field already has an implicit getter, plus a setter if it is not final; get and set let you write those accessors yourself.

    class CartItem {  CartItem(this.price, this._quantity);  final double price;  int _quantity;   int get quantity => _quantity;…
  8. 08

    When should a member be static, and what can static members not do?

    A static member belongs to the class itself, not to any object: there is one copy for the whole program.

  9. 09

    What is the difference between extends, implements and with, and where does an abstract class fit in?

    extends inherits a parent's implementation (one parent only), implements takes only its contract and makes you write every member, and with adds a mixin's members.

    abstract class Shape {  double get area; // abstract: subclasses must provide it  String describe() => 'area ${area.toStringAsFixed(1)}'; // inherited} mixin Loggable {…
  10. 10

    What is a mixin, when do you use one instead of inheritance, and what does the on clause do?

    A mixin is a reusable set of members you add to a class with with, even across unrelated hierarchies.

    mixin Validator {  // abstract: the host class must provide it  Map<String, String?> get fields;   bool get isValid => fields.values.every((v) => v != null && v.isNotEmpty);}…
  11. 11

    What are extension methods, what do they give you over helper functions, and when do they not apply?

    An extension adds methods, getters, setters or operators to a type you do not own (String, DateTime, BuildContext) without subclassing it.

    extension StringCase on String {  String capitalize() =>      isEmpty ? this : '${this[0].toUpperCase()}${substring(1)}';} extension ContextX on BuildContext {…
  12. 12

    How does operator overloading work in Dart, and which operators can you overload?

    You declare an instance method with the operator keyword, Money operator +(Money other), and from then on a + b calls it.

    final class Money {  const Money(this.cents);  final int cents;   Money operator +(Money other) => Money(cents + other.cents);  Money operator *(int times) => Money(cents * times);…
  13. 13

    This Set was meant to drop the duplicate but prints 2. What is missing, and what happens if you mutate an object already in the set?

    class Point {  Point(this.x, this.y);  int x, y;   @override  bool operator ==(Object other) =>      other is Point && other.x == x && other.y == y;} void main() {  final points = {Point(1, 2), Point(1, 2)};  print(points.length);}

    hashCode is missing: a Set or Map compares hash codes first and calls == only on a match, and two separate objects get different identity hashes.

  14. 14

    What is a callable class (a class with a call() method), and when would you write one?

    A class that defines a call method can be invoked like a function: getUserName('42') runs getUserName.call('42').

    abstract interface class UserRepository {  Future<String> nameOf(String id);} class GetUserName {  GetUserName(this._repo);…
  15. 15

    Why are Flutter widgets and models immutable, and how do you enforce immutability in Dart (final, @immutable, copyWith)?

    Flutter detects change by comparing the old object with the new one; mutate in place and they are the same object, so ValueNotifier, BLoC, updateShouldNotify and Riverpod's select see nothing.

    @immutableclass Profile {  Profile({required this.name, this.email, List<String> tags = const []})      : tags = List.unmodifiable(tags);   final String name;…
  16. 16

    What is a const constructor, and why does Flutter prefer const widgets?

    A const constructor lets an object be created at compile time; identical const expressions are canonicalized into one shared instance, so const Text('Hi') is the same object on every build.

    class Point {  const Point(this.x, this.y); // all fields final, no body  final int x, y;} void main() {…
  17. 17

    What do the Dart 3 class modifiers base, interface, final, sealed and mixin class control?

    They restrict what other libraries may do with a class: base allows only extending, interface only implementing, final neither, sealed fixes the set of subtypes so a switch can be exhaustive, and mixin class lets a class also be used after with.

    // interface: others implement it, nobody inherits your bodiesabstract interface class UserRepository {  Future<String> nameOf(String id);} // base: others may extend, never implement, so _send always runs…
  18. 18

    Why does Flutter favour composition over inheritance, and how would you refactor a bloated BaseScreen base class?

    Inheritance ties a subclass to the base's internals and call order, so one change in the base can break subclasses you never touched (the fragile base class problem).

    // before: class ProfileScreen extends BaseScreen { /* six overridden hooks */ } // after: the shared part is a widget the screens composeclass AsyncView<T> extends StatelessWidget {  const AsyncView({super.key, required this.snapshot, required this.builder});…
  19. 19

    Find the bug: in an integration test, ApiClient(baseUrl: server.url) still sends requests to the production API.

    class ApiClient {  ApiClient._(this.baseUrl);  final String baseUrl;   static ApiClient? _instance;   factory ApiClient({required String baseUrl}) =>      _instance ??= ApiClient._(baseUrl);   Uri endpoint(String path) => Uri.parse('$baseUrl$path');}

    The factory caches the first instance in a static field and silently ignores baseUrl on every later call, so whoever creates ApiClient first fixes the URL for the whole isolate: tests, flavors and a staging switch all get production.

  20. 20

    What does this code print, and why does swapping with Caching, Logging to with Logging, Caching change the output?

    class Base {  String fetch() => 'base';} mixin Caching on Base {  @override  String fetch() => 'cache(${super.fetch()})';}mixin Logging on Base {  @override  String fetch() => 'log(${super.fetch()})';} class Repo extends Base with Caching, Logging {} void main() => print(Repo().fetch());

    It prints log(cache(base)).

  21. 21

    Calling Screen('home') throws LateInitializationError. What is Dart's construction order, and how do you fix it?

    class Base {  Base() { onInit(); }  void onInit() {}} class Screen extends Base {  Screen(this.title) : upper = title.toUpperCase() {    controller = 'controller for $title';  }  final String title, upper;  late final String controller;   @override  void onInit() => print('$title $upper $controller');}

    When Base's constructor body runs, Screen is only half built.

  22. 22

    Find the bug: cart.contains(item) returns true or false for the same product depending on whether the cart holds the plain or the sale version.

    class Product {  Product(this.id);  final String id;  @override  bool operator ==(Object other) => other is Product && other.id == id;  @override  int get hashCode => id.hashCode;}class SaleProduct extends Product {  SaleProduct(super.id, this.discount);  final int discount;  @override  bool operator ==(Object other) =>      other is SaleProduct && other.id == id && other.discount == discount;  @override  int get hashCode => Object.hash(id, discount);}

    Product.== checks other is Product, which also accepts every SaleProduct, so a plain product equals its discounted version one way but not the other.