Advanced Flutter Interview: Part One

Advanced Flutter Interview: Part One
Photo by Maranda Vandergriff / Unsplash

I am thrilled to announce the initiation of a comprehensive series of articles designed to tackle advanced questions commonly encountered in Flutter interviews. As of now, I have assembled a carefully curated list of possible topics that will be explored in great depth. These subjects have been selected to cover a wide array of areas within Flutter, ensuring that both newcomers and seasoned veterans will find valuable insights.

The upcoming chapters will be presented in a Q&A format, allowing readers to engage with the material in a way that's both informative and interactive. By simulating the experience of an actual interview, I hope to provide practical guidance and real-world insights.

If you're interested in previewing the topics to be covered, I invite you to check out my Telegram channel or refer to a post on LinkedIn. Links to both can be easily found on my profile.

What makes Dart a perfect choice for Flutter

The main feature of Dart is the ability to compile to native executables, not just talk about how nice, neat and clean it is. When compared to Kotlin (non-native) or Java, they require a JVM installed on the device. Instead, dart ships its runtime along with the executable, which makes it attractive for a cross-platform framework like Flutter, and follows the notion of create once - run everywhere (without pre-installing anything). It is also a high-level OOP language with strong C-style typing, so should be familiar to a large number of developers.


What are the differences between const and final?

Both final and const are used for the creation of non-reassignable variables. But there are important differences. Creating a variable with the final simply means that it cannot be reassigned and that it is possible to change its fields. A typical example would be a list, if you defined it as final you'd still be able to add or remove elements.

When it comes to const, everything changes 180°. Simply put, they are compile-time constants. This means that the values of such constants must be known at compile time, which allows for many related optimisations. First of all, the value of a constant is immutable: once created, it can't be changed. Also, once a constant value is initialised, it is reused everywhere. For example, if class A has a const constructor, then if you create 100 instances of that class, only one will be created and only one reference will be shared across all variables. If you init two const string variables with the value "hello", only one string object will be created.

What about Flutter?

These const optimisations also apply to Flutter. When creating const widget instances, the framework can recognise that it is getting the same instance as before, and so the build method is not called again.


Tell about differences between local, static and global variables in Dart

Firstly, variables are scoped in Dart, as they are in many other languages. Simply put, the scope of a variable is how long it lives. This is one of the most valuable differences: global and static variables are accessible in all parts of the application when the local variable is limited by its scope.

What's more, both global and static variables are lazily initialized. This means that memory is not allocated for these variables until they are used in the code.


What is Dart VM?

Dart's VM plays a crucial role in executing Dart code, and it offers a unique blend of Just-In-Time (JIT) compilation and Ahead-Of-Time (AOT) compilation, depending on the situation.

In development mode, the Dart VM utilizes JIT compilation, allowing for features like hot reloading. This makes the development process more interactive and responsive, as developers can make changes to their code and see the results almost instantly without rebuilding the entire application. The VM starts with unoptimized code, but as the code runs, it optimizes frequently-used code paths with speculative optimizations.

  1. Kernel AST Binary: The Dart VM compiles Dart source code into an intermediate representation called Kernel AST Binary. The Kernel is a lower-level intermediate representation of the code, and the CFE (Common Front-End) is responsible for translating Dart source code to this form.
  2. JIT Compilation: This compilation technique compiles code during runtime, allowing for quick iteration and development. It enables dynamic analysis of the code as it runs, facilitating optimization based on actual program behaviour.
  3. Speculative Optimizations: The VM can make guesses about the likely execution path of the code and optimize it accordingly, such as through inline caching, which speeds up the method or function calling by avoiding repeated lookups.

In production mode, the Dart VM typically uses AOT compilation, where the code is compiled to native machine code ahead of time. This eliminates the need for a JIT compiler at run-time, improving startup time and overall performance. The AOT compilation pipeline reuses some parts of the JIT and performs some optimizations to produce more efficient code since it doesn't have the opportunity to use the tricks of the JIT.

AOT Compilation: By translating the Dart code directly to native machine code, AOT compilation provides quicker startup times and more predictable performance. This is often used for Flutter apps when building for release, where the responsiveness and speed of the application are critical.

The Dart VM, with its dual mode of JIT and AOT compilation, gives developers a flexible and efficient environment for both development and production, making it a unique and powerful tool in the Dart ecosystem.


What is Dart Runtime?

The dart runtime is a special environment where the Dart code exactly executes. Regardless of the platform used or the method of compilation, the execution of Dart code requires a Dart runtime. This runtime is included in both Dart VM and AOT. The runtime performs several important functions:

Memory management: Dart uses a managed memory model where a garbage collector (GC) reclaims unused memory.

Type system enforcement: While many of Dart's type checks are static (at compile time), some are dynamic (at runtime). The Dart runtime is responsible for these dynamic checks, enforcing them through type checking and casting operations.

Isolates management: Within the Dart environment, the runtime manages the main isolate (where code is typically executed) along with any additional isolates that the application may initiate.


What are the compilers of Dart?

In Dart, there are a few tools for compiling your code: Dart VM(offers JIT), dart2native(AOT), dart2js, dartdevc

💡
There is a tool called the CFE (Common FrontEnd) whose job is to compile Dart code into an intermediate representation called the kernel binary. The CFE is shared between many dart tools.

VM - Dart has a VM that supports a fast development cycle (hot reload, metrics, rich debugging support). It provides a JIT (just in time) compiler that supports incremental recompilation (enabling hot reload). The kernel binary from CFE is sent to the VM, parsed and all necessary objects are created. Note that only basic information about libraries and classes is loaded initially, allowing details to be lazily loaded as required. At the moment, it has the highest peak performance because it has the chance to analyse the execution of the program and perform optimisations at runtime. But at the same time, we sacrifice the time it takes to warm up.

AOT - VM has an AOT compilation pipeline which produces an efficient, high-performance, self-contained executable compiled to machine code. A special, stripped version of the Dart VM is included. This is called precompiled runtime. Because the process is done in advance, the compiler can make some attempts to optimise the program. For example, it can inline method calls and go through a tree-shaking process, discarding any useless classes, structures and generally saying code. This type of compilation has the best startup time and efficient execution, but it's inferior to the JIT at peak because it can't analyse the program at runtime.

Dart offers two compilers (transpilers) for JavaScript. dart2js produces efficient but not very readable code, as it aims to be the most efficient and fast. On the other side, dartdevc is like a JIT and offers fast & incremental compilation and human-readable JS code that mirrors the Dart structure.


How does asynchrony work in Dart?

To begin with, it's important to understand that Dart is a programming language that can only perform one task at a time due to being single-threaded. This means it has just one call stack. However, despite this limitation, it can still handle multiple operations simultaneously, such as scrolling, tapping, network calls, and I/O operations. This is made possible by the Event Loop, which plays a vital role in enabling asynchrony in Dart.

The logic is easy as pie. There are two FIFO (first in first out) queues where elements are being processed one by one: microtask and event. The Microtask queue has precedence over the event queue. The runtime processes microtasks until they are empty, then comes to events and repeats.

This is how the event loop works in general. Moving further, you can use Future or scheduleMicrotask to schedule operations on the event loop. Also, you can mark a method or function as async: it means that it returns future. When their time comes they will be executed and appear in the call stack.


What does await keyword mean?

Dart comes with await syntax sugar which makes the code behave synchronously, but at the same time it is not blocking. Actually, all the code after await can be rewritten with then callbacks. Await makes code more understandable, increasing readability as there is no use for nesting thousands of then callbacks.


What about timers in Dart? How to delay an operation?

There is a primarily used API for delaying an operation in Dart. It is known as Future.delayed. You may wonder how this work under the hood. Well, this one uses another Dart Core API called Timer, which implements all the logic. So when you use Future.delayed, you are actually creating a timer that completes the future as it is fired. At the same time, the timer functionality is implemented on the native side.

When you schedule a delayed operation in Dart, like Future.delayed(Duration(seconds: 1)), what typically happens is:

  1. Dart makes a call into the native code to register a timer.
  2. The native code takes care of waiting for the specified duration.
  3. When the timer expires, the native code places the Dart callback associated with the timer onto Dart's event queue.
  4. Dart's event loop eventually picks up that callback and runs it.

Is it possible to make the event loop stuck? How to fix it?

Of course, it is possible. When some synchronous code is executed, it is impossible for the event loop to process the queues. At that moment, the event loop is stuck, and basically, the application is stuck too, it can't do anything, it's just waiting for the heavy computation to finish. This can happen if you are decoding JSON, building models, manipulating large arrays, or performing cryptographic operations.

Although Dart is a single-threaded language you can spawn an isolate (which is a thread itself with some limitations) and move the heavy computation into it. Also, I want to note that if you have any chances to use async operations - use them! A common example would be I/O and network calls.

Also, a common example would be processing the array. Imagine you are making a network request that ought to return a list of 100 elements. What will you do? Basically, perform JSON decoding and then map it to a list of models. However, this process can block the execution for a while. Instead, you can make use of Stream and process these elements lazily giving the event loop a possibility to operate others. Take a look here.


Thanks for reading me!

I hope you found the initial part about Dart valuable. We've mostly delved into Dart-related topics so far, but in the upcoming articles, we will also explore Flutter. In fact, I have prepared around 60 questions, so there's a wealth of content on the way, likely spanning up to five or even more chapters. To stay updated with me and make sure you don't miss anything, please follow or subscribe to my social media channels.

Resources

Dart Authors. Overview - https://dart.dev/overview
Dart Authors. Dart compile - https://dart.dev/tools/dart-compile
Vyacheslav Egorov. Introduction to Dart VM - https://mrale.ph/dartvm/#introduction-to-dart-vm
Philip Robberts. "What's the heck is the event loop anyway?" - https://www.youtube.com/watch?v=8aGhZQkoFbQ&ab_channel=JSConf

Read more