Home · Flutter Tutorials

Flutter Interview Questions and Answers

In this article, you’ll work through a series of Flutter and Dart job interview questions and answers.

5/5 12 Ratings

Version

  • Dart 2.7, Flutter, VS Code

Flutter is a relatively new framework for building cross-platform apps, but its popularity is rapidly increasing. Employers recognize the benefits of a single code base that lets them merge two or three teams down to one. The number of jobs for Flutter developers is on the rise.

In this article, you’ll work through a series of Flutter and Dart job interview questions and answers.

If you’re a developer who’s looking for a new job, work through each of the questions below. Try to answer on your own before you look at the answer. This can help you identify areas where you can strengthen your skills.

If you’re here as a potential employer, browse through the questions to get ideas for what to ask your candidates.

Everyone else — have fun testing your own Flutter and Dart knowledge! :]

The questions are separated into three levels:

  • Junior: Suitable for a junior developer in Flutter. You’re familiar with the basics, and you’ve made a few sample apps.
  • Intermediate: Suitable for an intermediate developer with a strong interest in how Flutter and Dart work. You’ve read a lot and experimented even more.
  • Senior: Suitable for a senior-level developer. This is someone who enjoys thoroughly exploring the Flutter framework and Dart language and knows how to manage a project.

At each level, you’ll find two types of questions:

  • Written questions: Good for emailed or online programming tests, since they involve writing code.
  • Verbal questions: Good to ask on a video call or in a face-to-face interview.

While you work through the questions, open your favorite IDE.

Junior Written Questions

Question 1

Given the following class:

class Recipe {
  int cows;
  int trampolines;

  Recipe(this.cows, this.trampolines);
  
  int makeMilkshake() {
    return cows + trampolines;
  }
}

Convert makeMilkshake() to a getter called milkshake using the shorthand “fat arrow” syntax.

[spoiler title=”Solution”]
If a method contains only a single line of code, you can reduce the number of lines of code by returning the result using the => syntax:

methodName(parameters) => statement;

Note that you don’t use the keyword return when using =>.

The makeMilkshake() conversion would be:

int get milkshake => cows + trampolines;

[/spoiler]

Question 2

Given the following widget:

class MyWidget extends StatelessWidget {
  final personNextToMe = 'That reminds me about the time when I was ten and our neighbor, her name was Mrs. Mable, and she said...';

  @override
  Widget build(BuildContext context) {
    return Row(children: [
      Icon(Icons.airline_seat_legroom_reduced),
      Text(personNextToMe),
      Icon(Icons.airline_seat_legroom_reduced),
    ]);
  }
}

There is a text overflow on some narrow devices:

text overflow

How would you fix this?

[spoiler title=”Solution”]

Expanded(
  child: Text(
    personNextToMe,
  ),
),

Wrapping the Text widget with an Expanded widget tells Row to ignore the Text widget’s intrinsic width and assign it a width based on the remaining space in the row.

Using more than one Expanded widget in a Row, Column or Flex evenly splits the space among all the Expanded widgets. Use flex to prioritize space allocations when there’s more than one Expanded widget.

If you also used the Text widget’s overflow property, then bonus points for you.

Read more about layout constraints in the Flutter docs.
[/spoiler]

Question 3

Refactor the code below so that the children of Row will wrap to the next line when the display width is too narrow for them to fit.

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Row(children: [
      Chip(label: Text('I')),
      Chip(label: Text('really')),
      Chip(label: Text('really')),
      Chip(label: Text('really')),
      Chip(label: Text('really')),
      Chip(label: Text('really')),
      Chip(label: Text('really')),
      Chip(label: Text('need')),
      Chip(label: Text('a')),
      Chip(label: Text('job')),
    ]);
  }
}

[spoiler title=”Solution”]

All you need to do is replace Row with Wrap.

Read more about the Wrap widget in the Medium article, Flutter Wrap Widget.
[/spoiler]

Question 4

You’ve declared list1 with var, list2 with final and list3 with const. What’s the difference between these keywords? Will the last two lines compile?

var list1 = ['I', '💙', 'Flutter'];

final list2 = list1;
list2[2] = 'Dart';   // Will this line compile?
  
const list3 = list1; // Will this line compile?

[spoiler title=”Solution”]
When using the var keyword, the Data type is inferred and its value can change. The following line is equivalent to the first line above, except that you explicitly declare the data type:

List<String> list1 = ['I', '💙', 'Flutter'];

With final and const, you can’t reassign a new value after the initial assignment. final values are assigned once at runtime and a const variable value has to be either known at compile time, set, or hard coded before you run your app.

The third line will compile. You’re not reassigning the list2 list itself, but changing the value of an item in the third index position (remember, indexes start with 0). Lists are mutable by default in Dart.

If you tried to do the following, though, it wouldn’t compile because you’re trying to reassign a final variable:

list2 = ['I', '💙', 'Dart'];

The fourth line will not compile because the value of list1 isn’t assigned until runtime. Read Dartlang’s article, Const, Static, Final, Oh my!, to learn more.
[/spoiler]

Question 5

Given the following class:

class Pizza {
  String cheese = 'cheddar';
}

How would you make cheese private? How would you make it a global variable? When should you use globals?

[spoiler title=”Solution”]

Prefixing a variable with an underscore _ makes it private within the library.

class Pizza {
  String _cheese = 'cheddar';
}

Dart doesn’t have the concept of class private variables. A library is generally a file and a file can contain multiple classes.

If you want to make a global variable, just move it outside of the class:

String cheese = 'cheddar';

Putting it outside the class makes it a top-level variable, which is available anywhere you import the file.

Global variables are generally frowned upon because it’s easy to lose track of what’s changing them. This makes debugging and testing difficult. However, they can be useful sometimes, like when:

  • Hacking together a quick demo that you aren’t going to maintain.
  • Creating Singletons to provide services like a database or network authenticator.
  • Making const variables to share things like colors, dimensions, styles and themes. These types of global variables are often stored in a separate file, like constants.dart, which the libraries then import.

See the Dart language’s library and visibility documentation for more details.
[/spoiler]

Junior Verbal Questions

Question 1

What’s the difference between hot reload and hot restart?

[spoiler title=”Solution”]

Hot reload maintains the app state while updating the UI almost instantaneously. Hot restart, by comparison, takes a little longer because it resets the app state to its initial conditions before updating the UI. Both of these are faster than doing a full restart, which requires recompiling the app.

When making significant changes, you need to stop and restart the app. On rare occasions, you might have to delete the app from your simulator/emulator or device and reinstall it.
[/spoiler]

Question 2

What is the difference between StatelessWidget and StatefulWidget?

[spoiler title=”Solution”]

StatelessWidget is an immutable class that acts as a blueprint for some part of the UI layout. You use it when the widget doesn’t change while displaying and, therefore, has no State.

StatefulWidget is also immutable, but it’s coupled with a State object that allows you to rebuild the widget with new values whenever calling setState(). Use StatefulWidget whenever the UI can change dynamically.

If the state becomes more complex or the same state is in two different widgets, then you should consider a more sophisticated state management solution.

You can read more about stateless and stateful widgets in the Flutter docs.
[/spoiler]

Question 3

What is the difference between WidgetsApp and MaterialApp?

[spoiler title=”Solution”]
WidgetsApp provides basic navigation. Together with the widgets library, it includes many of the foundational widgets that Flutter uses.

MaterialApp and the corresponding material library is a layer built on top of WidgetsApp and the widgets library. It implements Material Design, which gives the app a unified look and feel on any platform or device. The material library has many additional widgets that come with it.

You certainly aren’t required to use MaterialApp in your project. You can use CupertinoApp to make iOS users feel at home, or you can even build your own set of custom widgets to fit your brand.
[/spoiler]

Question 4

Can you nest a Scaffold? Why or why not?

[spoiler title=”Solution”]
Yes, you can absolutely nest a Scaffold. That’s the beauty of Flutter. You control the entire UI.

Scaffold is just a widget, so you can put it anywhere a widget might go. By nesting a Scaffold, you can layer drawers, snack bars and bottom sheets.

Sample app with nested scaffold widgets
[/spoiler]

Question 5

When is it appropriate to use packages, plugins or third-party dependencies?

[spoiler title=”Solution”]
Packages and plugins are great for saving you time and work. There’s no need to solve a complex problem yourself when someone else has done it already, especially if the solution is highly rated.

On the other hand, there’s also a danger of being too reliant on third party packages. They can break, have bugs or even be abandoned. When you need to switch to a new package down the road, you might have to make huge changes to your code.

That’s why it’s important to isolate packages from your core business logic. You can do that by creating an abstract Dart class that acts as an interface for the package. Once you’ve set up that kind of architecture, all you have to do to switch packages is to rewrite the concrete wrapper class that implements your interface.
[/spoiler]

Intermediate Written Questions

Question 1

You’re making a shopping app called RubberBaby, which sells dolls. Unfortunately, you’ve run into a problem on the order page. If a customer makes one order for blue dolls and another order for red dolls but then tries to delete the blue doll order, the red doll order is wrong.

When the user deletes the blue doll order, the red doll order incorrectly takes the amount of the blue doll order

Given only the following code, how would you fix the RubberBaby buggy buttons?

class OrderPage extends StatefulWidget {
  @override
  _OrderPageState createState() => _OrderPageState();
}

class _OrderPageState extends State<OrderPage> {
  bool isShowing = true;
  @override
  Widget build(BuildContext context) {
    return Column(children: [
      RaisedButton(
        child: (Text('Delete blue')),
        onPressed: () {
          setState(() {
            isShowing = false;
          });
        },
      ),
      if (isShowing) CounterButton(color: Colors.blue),
      CounterButton(color: Colors.red),
    ]);
  }
}

[spoiler title=”Solution”]

When you have a stateful widget and something about the widget tree changes, the framework compares widget types to see what it can reuse.

Since both CounterButton widgets are of the same type, Flutter doesn’t know which widget to assign the state to. That results in the red button updating with the blue button’s internal counter state.

To address this, use the key property for each widget. This property adds an ID for the widget:

CounterButton(
  key: ValueKey('red'),
  color: Colors.red,
),

By adding key, you’ve uniquely identified the red counter button and Flutter will be able to preserve its state. You can read more about using keys in the Medium article, Keys! What are they good for?.
[/spoiler]

Question 2

GitHub Jobs has an open API for querying software engineering-related job positions. The following URL returns a list of remote jobs:

https://jobs.github.com/positions.json?location=remote

Given the following simple data class, in which you only care about the company name and job title, write a function that returns a Future with a List of Jobs. You can ignore error checking for this question.

class Job {
  Job(this.company, this.title);

  final String company;
  final String title;
}

[spoiler title=”Solution”]
Since the API returns a list of JSON maps, adding a fromJson constructor to Job will make your life easier:

class Job {
  Job(this.company, this.title);

  Job.fromJson(Map<String, dynamic> json)
      : company = json['company'],
        title = json['title'];

  final String company;
  final String title;
}

There are a number of packages that you could use to make HTTP requests, but the Dart team maintains the basic http package. To use it, add the dependency in pubspec.yaml:

dependencies:
  http: ^0.12.1

You import the package and create a function to pull the data from GitHub Jobs in the background:

import 'dart:convert';
import 'package:http/http.dart' as http;

Future<List<Job>> fetchJobs() async {
  final host = 'jobs.github.com';
  final path = 'positions.json';
  final queryParameters = {'location': 'remote'};
  final headers = {'Accept': 'application/json'};
  final uri = Uri.https(host, path, queryParameters);
  final results = await http.get(uri, headers: headers);
  final jsonList = json.decode(results.body) as List;
  return jsonList.map((job) => Job.fromJson(job)).toList();
}

After defining the Uri statement, you make the http.get request, which returns a JSON string.

Next, using json.decode the JSON results are parsed into a map, which is converted to a List of Job objects.

Our article, Parsing JSON in Flutter, will teach you more about using a web API, making models and more advanced JSON parsing.
[/spoiler]

Question 3

Given a Dart stream that produces an unending series of strings that can be either salmon or trout:

final fishStream = FishHatchery().stream; 
// salmon, trout, trout, salmon, ...

Transform the stream so it returns the string sushi only for the first five instances of salmon.

[spoiler title=”Solution”]
The transformed stream looks like this:

final fishStream = FishHatchery().stream;
final sushiStream = fishStream
    .where((fish) => fish == 'salmon')
    .map((fish) => 'sushi')
    .take(5);

If you’d like to play with the code more, here’s the FishHatchery class:

class FishHatchery {
  FishHatchery() {
    Timer.periodic(Duration(seconds: 1), (t) {
      final isSalmon = Random().nextBool();
      final fish = (isSalmon) ? 'salmon' : 'trout';
      _controller.sink.add(fish);
    });
  }

  final _controller = StreamController<String>();
  Stream<String> get stream => _controller.stream;
}

You can learn more about streams in the Flutter’s team video, Dart Streams — Flutter in Focus and in the Dart Creating Streams docs.
[/spoiler]

Question 4

Why would the following code block your Flutter app?

String playHideAndSeekTheLongVersion() {
  var counting = 0;
  for (var i = 1; i <= 1000000000; i++) {
    counting = i;
  }
  return '$counting! Ready or not, here I come!';
}

Would making it an async function help?

[spoiler title="Solution"]
It blocks your app because counting to ten billion is a computationally expensive task, even for a computer.

Dart code runs inside its own area of memory called an isolate — also known as memory thread. Each isolate has its own memory heap, which ensures that no isolate can access any other isolate's state.

Making it an async function wouldn't help, either, because it would still run on the same isolate.

Future<String> playHideAndSeekTheLongVersion() async {
  var counting = 0;
  await Future(() {
    for (var i = 1; i <= 10000000000; i++) {
      counting = i;
    }
  });
  return '$counting! Ready or not, here I come!';
}

The solution is to run it on a different isolate:

Future<String> makeSomeoneElseCountForMe() async {
  return await compute(playHideAndSeekTheLongVersion, 10000000000);
}

String playHideAndSeekTheLongVersion(int countTo) {
  var counting = 0;
  for (var i = 1; i <= countTo; i++) {
    counting = i;
  }
  return '$counting! Ready or not, here I come!';
}

This would not block your UI.

You can read more about asynchronous tasks and isolates in the Flutter team's video, Isolates and Event Loops — Flutter in Focus and also in didierboelens.com's article, Futures — Isolates — Event Loop.

You're also going to get another dose of isolates in the next question.
[/spoiler]

Intermediate Verbal Questions

Question 1

What is the event loop, and what is its relationship to isolates?

[spoiler title="Solution"]
Dart was an early adopter of social distancing. Dart code runs on a single thread called an isolate. Separate isolates don't hang out together — the most they do is text each other. In computer-speak, you'd say that isolates don't share any memory and they only communicate through messages sent over ports.

Every isolate has an event loop, which schedules asynchronous tasks to run. The tasks can be on one of two different queues: the microtask queue or the event queue.

Microtasks always run first, but they are mainly internal tasks that the developer doesn't need to worry about. Calling a future puts the task on the event queue when the future completes.

A lot of new Dart programmers think async methods run on a separate thread. Although that may be true for I/O operations that the system handles, it isn't the case for your own code. That's why if you have an expensive computation, you need to run it on a separate isolate.

Read more about isolates, event loops, and concurrency in the Medium article, Dart asynchronous programming: Isolates and event loops and Futures — Isolates — Event Loops.
[/spoiler]

Question 2

How do you reduce widget rebuild?

[spoiler title="Solution"]
You rebuild widgets when the state changes. This is normal and desirable, because it allows the user to see the state changes reflected in the UI. However, rebuilding parts of the UI that don't need to change is wasteful.

There are several things you can do to reduce unnecessary widget rebuilding.

  • The first is to refactor a large widget tree into smaller individual widgets, each with its own build method.
  • Whenever possible, use the const constructor, because this will tell Flutter that it doesn't need to rebuild the widget.
  • Keep the subtree of a stateful widget as small as possible. If a stateful widget needs to have a widget subtree under it, create a custom widget for the stateful widget and give it a child parameter.

Read more about performance considerations in the Flutter docs.
[/spoiler]

Question 3

What is BuildContext and how is it useful?

[spoiler title="Solution"]
BuildContext is actually the widget's element in the Element tree — so every widget has its own BuildContext.

You usually use BuildContext to get a reference to the theme or to another widget. For example, if you want to show a material dialog, you need a reference to the scaffold. You can get it with Scaffold.of(context), where context is the build context. of() searches up the tree until it finds the nearest scaffold.

Read didierboelens.com's article, Widget — State — Context — Inherited Widget, to not only learn about the build context, but also the stateful widget life cycle and inherited widgets.

Additionally, our article, Flutter Text Rendering, takes you on a low-level tour of the Flutter source code, where you'll meet build context, elements and even render objects.
[/spoiler]

Question 4

How do you talk to native code from within a Flutter app?

[spoiler title="Solution"]
Normally you don't need to talk to native code because the Flutter framework or third party plugins handle it. However, if you do find yourself needing to get special access to the underlying platform, you can use platform channels.

One type of platform channel is a method channel. Data is serialized on the Dart side and then sent to the native side. You can write native code to interact with the platform before sending a serialized message back. That message might be written in Java or Kotlin on Android or Objective-C or Swift on iOS.

You don't use platform channels on the web, however, because they're an unnecessary step.

The second type of platform channel is the event channel, which you use to send a stream of data from the native platform back to Flutter. This is useful for monitoring sensor data.

The Flutter docs have more details about platform channels.
[/spoiler]

Question 5

What types of tests can you perform?

[spoiler title="Solution"]
There are three main kinds of tests: unit tests, widget tests and integration tests. Unit tests are all about checking the validity of your business logic. Widget tests are for making sure UI widgets have the components that you expect them to. Integration tests check that your app is working as a whole.

One additional type of test that is not as well known is a golden test. In a golden test, you have an image of a widget or screen and check to see that the actual widget matches it.

Learn more about testing in the Flutter Cookbook docs and more on golden tests from the Medium article, Flutter: Golden tests — compare Widgets with Snapshots.

Also, raywenderlich.com has an article about Flutter unit testing.
[/spoiler]

Senior Written Questions

Question 1

Demonstrate Dart isolate communication using ports by completing the following steps:

  1. Give a function called downloadAndCompressTheInternet() to a new isolate.
  2. Have it return the value 42.

[spoiler title="Solution"]

import 'dart:isolate';

void main() async {
  // 1
  final receivePort = ReceivePort();
  // 2
  final isolate = await Isolate.spawn(
    downloadAndCompressTheInternet,
    receivePort.sendPort,
  );
  // 3
  receivePort.listen((message) {
    print(message);
    receivePort.close();
    isolate.kill();
  });
}

// 4
void downloadAndCompressTheInternet(SendPort sendPort) {
  sendPort.send(42);
}

In this code, you:

  1. Create a port for receiving data from the new isolate.
  2. Create a new isolate, give it some work to do and provide it a means to send data back.
  3. Listen for any data message that the new isolate sends, then get rid of the isolate.
  4. Send the data back using the port that the main isolate is listening to.

The internet decompression algorithm is still under development. :]

Read Coding With Joe's article, Dart Fundamentals — Isolates, to learn more about isolate communication.
[/spoiler]

Question 2

You have two tree data structures, where random integers are nodes in the tree. The numbers don't have to be unique, nor are they sorted in any logical way. Both trees are an arbitrary number of levels deep. Write an algorithm to identify any numbers in the first tree that are not in the second.

Here's an example:

Diagram showing two tree data structures containing random numbers

The algorithm should identify that the number 1 is in the first tree, but not in the second.

[spoiler title="Solution"]
First define the nodes in the tree:

class Node {
  int data;
  List<Node> children;

  Node(this.data, {this.children});
}

Add the logic to search the tree recursively, looking for unique integers:

class UniqueTreeItems {
  final Set<int> _uniqueIntegers = HashSet<int>();

  Set<int> search(Node tree) {
    _addInOrder(tree);
    return _uniqueIntegers;
  }

  void _addInOrder(Node node) {
    _uniqueIntegers.add(node.data);
    if (node.children == null) return;
    for (final child in node.children) {
      _addInOrder(child);
    }
  }
}

Set up the test data:

final treeOne = Node(1, children: [
  Node(4, children: [
    Node(10),
    Node(12),
  ]),
  Node(3, children: [
    Node(3),
    Node(10),
    Node(1),
  ]),
]);

final treeTwo = Node(4, children: [
  Node(10),
  Node(3),
  Node(12),
]);

Filter out any integers from Tree 1 that are also in Tree 2:

void main() async {
  final uniqueOne = UniqueTreeItems().search(treeOne);
  final uniqueTwo = UniqueTreeItems().search(treeTwo);
  final answer = uniqueOne.where((element) => !uniqueTwo.contains(element));
  answer.forEach(print); // 1
}

The answer is 1.
[/spoiler]

Senior Verbal Questions

Question 1

What are the pros and cons of different state management solutions?

[spoiler title="Solution"]
While there are countless varieties, some of the more popular state management solutions include BLoC, ChangeNotifier with Provider, Redux, MobX and RxDart. These are all appropriate for medium- to large-scale apps; if you're only making a quick demo app, then a stateful widget is often enough.

Instead of listing the pros and cons of each state management option, it's more useful to look at the situations where a certain class of solutions is a better fit. For example, for someone who's overwhelmed with the sheer number of options, it's important to choose a solution that's easy to grasp, mentally. ChangeNotifier with Provider or MobX would be a good choice, because it makes sense to directly call methods on the state class in response to events.

If you're heavily reliant on streams, such as with a Firebase API, then it's natural to choose a stream-based solution like BLoC or RxDart.

And if you need undo/redo functionality, then you'd want a solution like BLoC or Redux that handles immutable state well.

In the end, a lot of it comes down to personal preference. You can find links to more information about the most popular state management systems in Flutter's' list of state management approaches.

There are also articles about BLoC and Provider with ChangeNotifier here on raywenderlich.com.
[/spoiler]

Question 2

How would you design an app to control an elevator?

[spoiler title="Solution"]
This question tests your analytical skills, organization and use of SOLID principles.

Here's one possible answer:

  1. First, determine what the core functionality is: things like opening and closing the doors, moving up and down to different floors, calling for help and coordinating with other elevators. This is your business logic. Drawing a diagram may help.
  2. Implement the business logic in Test Driven Development (TDD) style. That is, write a failing test, write just enough business logic code to make it pass, refactor and then do it all again with another test.
  3. In the beginning, it doesn't matter if you have physical buttons or a Flutter-powered touch screen. It doesn't matter what the elevator looks like or where it is. It doesn't matter what the emergency call system is. You should abstract these external factors behind interfaces that you can mock out during development.
  4. Once you've completed the core logic, you can work on implementing each of the components that you previously only represented with an interface. For the UI, you'll need to set up a state management system that takes in events like button pushes or arrivals and then updates the state, which could result in lighting a button number or updating a screen. You'll also need to implement the services that interact with the system for making an emergency call or the hardware that opens the doors.
  5. Safety is obviously extremely important for elevators, so in addition to testing the core business logic and the various system components in isolation, you'll also need to do thorough integration testing. For an elevator, that will involve manual testing by robots and/or humans.

[/spoiler]

Where to Go From Here?

Congratulations! You made it to the end. Don't feel bad if you didn't know all the answers. I had to do a lot of research myself while writing them.

Consider this a jumping-off point. Make note of any areas where you're weak, then do more research in those areas. Reading through the Flutter documentation and Dart guides will teach you a lot.

If you're looking to learn more Dart, check out our video course on the fundamentals of Dart. Also, here at raywenderlich.com we're continuously putting out new content on all things Flutter.

If you have more interview question suggestions, a better answer, or even a code challenge, please leave them in the comments below.

Good luck with your job hunt!

Average Rating

5/5

Add a rating for this content

12 ratings

More like this

Contributors

Comments