skip to content

In Flutter, what is the difference between Expanded and Flexible inside a Row or Column, and what does FlexFit change?

level: middleimportance: must knowfreq 76%

answer

  1. inflexible children sized first
  2. flex factor defaults to 1
  3. Expanded means FlexFit.tight
  4. Flexible defaults to FlexFit.loose
  5. unused share is not redistributed

basics

~20 s

Expanded is Flexible with FlexFit.tight: the child must fill its share of the leftover main-axis space. Flexible defaults to FlexFit.loose: the child may be smaller than its share, and the unused space stays free instead of going to siblings.

solid answer

~40 s

A `Row` or `Column` first lays out inflexible children with an unbounded main axis, subtracts their sizes (and any `spacing`) from its main-axis maximum, then splits what remains among flexible children in proportion to their `flex` factors, which default to 1. `FlexFit` decides how a child uses its share. `Expanded` is `Flexible` with `fit: FlexFit.tight`: min and max both equal the share, so the child fills it exactly. Plain `Flexible` defaults to `FlexFit.loose`: min 0, max the share, so the child can be smaller, such as a short `Text`. Its unused space is not handed to siblings; it becomes free space that `mainAxisAlignment` places. On a nutrition-label row I'd give the bar `Expanded` so it always spans, and the name `Flexible` so a short name doesn't reserve empty width.

code

dart · 24 lines
dart
import 'package:flutter/material.dart';

class NutrientRow extends StatelessWidget {
  const NutrientRow({super.key, required this.name, required this.percent, required this.amount});

  final String name;
  final double percent; // 0.0 - 1.0 of daily value
  final String amount;

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Expanded(
          flex: 2,
          child: Text(name, maxLines: 1, overflow: TextOverflow.ellipsis),
        ),
        Expanded(flex: 3, child: LinearProgressIndicator(value: percent)),
        const SizedBox(width: 8),
        Text(amount), // inflexible: always shown in full
      ],
    );
  }
}

go deeper

for a junior

Recall that Expanded fills the remaining space and that the flex factor, default 1, sets the proportions.

for a middle

Walk through the algorithm: inflexible children first, free space divided by total flex, tight versus loose minimums, and why unused loose space is not redistributed.

for a senior

Choose tight or loose per child from the design intent, such as aligned bars across rows versus text that should hug its content, and diagnose ParentDataWidget misuse.

for a principal

Set component guidelines on when list rows use fixed columns versus flexible shares, balancing visual alignment across rows against content-driven sizing.

## Flexible children in a flex layout `Row`, `Column` and `Flex` distinguish two kinds of children: - **Inflexible** children — anything not wrapped in `Flexible`, `Expanded` or `Spacer`. They are measured at their natural size. - **Flexible** children — wrapped in `Flexible` or its subclass `Expanded`, carrying a `flex` factor greater than zero. They share whatever main-axis space is left. `Flexible` and `Expanded` are not layout widgets themselves: they are `ParentDataWidget`s that write a `flex` factor and a `fit` onto their child's parent data for the enclosing `Flex` to read. That is why they must be direct children of a `Row`, `Column` or `Flex`; anywhere else Flutter reports 'Incorrect use of ParentDataWidget.' ## The algorithm in order The `RenderFlex` documentation describes layout in six steps. The ones that matter here: 1. **Lay out inflexible children** with an unbounded main axis and the incoming cross-axis constraints. Add up their main-axis sizes, plus `spacing` between children. 2. **Compute the free space**: the main-axis maximum minus that total, floored at zero. 3. **Divide it by the total flex**: each flexible child's share is `freeSpace / totalFlex * flex`. 4. **Lay out each flexible child** with its share as the main-axis maximum. The fit decides the minimum. 5. **Size the Row or Column** according to `mainAxisSize`, then **position children** with `mainAxisAlignment` and `crossAxisAlignment`. Worked example: a 300-pixel `Row` with a fixed 60-pixel icon and two `Expanded` children with flex 1 and 2. Free space is 240; per unit of flex that is 80; the children get 80 and 160. ## FlexFit: tight versus loose | | `Expanded` | `Flexible` (default) | |---|---|---| | `fit` | `FlexFit.tight` (fixed) | `FlexFit.loose` (can be set to tight) | | Child's main-axis min | its full share | 0 | | Child's main-axis max | its full share | its full share | | Child smaller than share? | not allowed | allowed | | `flex` default | 1 | 1 | `Expanded(child: x)` is exactly `Flexible(fit: FlexFit.tight, child: x)`. The difference shows only when the child's natural size is smaller than its share: - Under `Expanded`, a short `Text` is forced to the full share (its box is wide, its glyphs sit at the start). - Under `Flexible`, the `Text` takes only the width it needs. The unused part of a loose child's share is **not redistributed** to its siblings. The shares are computed once, before any flexible child is laid out. The leftover becomes free space, and `mainAxisAlignment` decides where it goes. ## Choosing on a nutrition-label row Each row of the label shows a nutrient name, a bar for the percentage of daily value and the amount: - **Amount** (`'4.5 g'`): inflexible. It should always show in full. - **Bar**: `Expanded(flex: 3)`. A bar has no natural width, so it must be told to fill its share, and a tight fit keeps all rows' bars starting and ending consistently. - **Name**: `Expanded(flex: 2)` if the names should form a column of equal width across rows; `Flexible` if a short name should not reserve empty width. With either, a long name now has a bounded width, so `maxLines: 1` and `TextOverflow.ellipsis` can truncate it instead of overflowing. ## Spacer and flex factors `Spacer` is shorthand for an `Expanded` with an empty child: `Spacer(flex: 2)` takes two units of free space and pushes its neighbours apart. Flex factors are integers, and a `flex` of 0 or `null` makes a child inflexible. ## Mistakes interviewers look for - Wrapping a widget in `Expanded` outside a `Flex` (inside `Padding`, `Stack` or `Wrap`). - Expecting `Flexible`'s unused space to go to another `Flexible` sibling. - Using `Expanded` in a parent with an unbounded main axis, which has no finite share to fill. - Believing `Expanded` 'makes a widget as big as possible' in general: it fills only the leftover share along the main axis.

  • In Flutter, what happens if you put Expanded directly inside a Padding that sits in a Row?
    `Expanded` is a `ParentDataWidget` that must be a direct child of a `Flex`. With a `Padding` in between, its parent data cannot be applied and Flutter reports 'Incorrect use of ParentDataWidget.' Swap the nesting: `Expanded(child: Padding(...))`, so the `Expanded` is the Row's direct child.
  • Can you make Flexible behave exactly like Expanded?
    Yes. `Expanded` is a `Flexible` whose `fit` is fixed to `FlexFit.tight`, so `Flexible(fit: FlexFit.tight, child: x)` lays out identically to `Expanded(child: x)`. `Expanded` exists because the tight fit is the common case and reads more clearly.

It is like splitting a restaurant bill: fixed-price dishes are paid first, then the remainder is divided by shares. An Expanded diner must spend their whole share; a Flexible diner may spend less, and their change stays on the table rather than going to the others.

saying these in an interview costs you the question

  • Expanded and Flexible are identical apart from the name.
  • A Flexible child's unused share goes to its flexible siblings.
  • Expanded makes a widget as big as the whole screen.
  • Flex space is shared before inflexible children are measured.
  • Expanded works when wrapped inside Padding or Container within a Row.