In Flutter's BoxConstraints, what distinguishes tight, loose and unbounded constraints, and which common widgets hand out each?
answer
- min equals max
- min of zero
- max is double.infinity
- root, Center, Row main axis
- forces an infinite width
basics
~20 sTight constraints have min equal to max, so one size is legal; loose ones have a zero minimum; unbounded ones have an infinite maximum. The root is tight, Center and Align loosen, and a Row or scroll view leaves its main axis unbounded.
solid answer
~40 sEach axis of a `BoxConstraints` is classified separately. **Tight**: min equals max, as the root view or a sized parent without alignment gives, and as a vertical list gives on its cross axis. **Loose**: min is zero, as `Center`, `Align` or `BoxConstraints.loose(size)` give. **Unbounded**: max is `double.infinity`, as a `Row` gives its non-flexible children on the main axis, a scroll view gives along its scroll axis, and `UnconstrainedBox` gives on both axes. The categories combine: an axis can be tight on width and loose on height. Unbounded is where bugs live: a widget that tries to fill its maximum has nothing to fill, and `SizedBox(width: double.infinity)` under an unbounded width makes the minimum infinite, which debug mode rejects with 'BoxConstraints forces an infinite width.'
code
dart · 16 linesimport 'package:flutter/material.dart';
Widget fullWidthInColumn() => const Column(
children: [
// Column bounds the width: infinity is clamped to the column's max.
SizedBox(width: double.infinity, height: 48, child: ColoredBox(color: Colors.indigo)),
],
);
Widget fullWidthInRow() => const Row(
children: [
// Row leaves its main axis unbounded: min width stays infinite and
// debug layout fails with 'BoxConstraints forces an infinite width.'
SizedBox(width: double.infinity, height: 48, child: ColoredBox(color: Colors.indigo)),
],
);go deeper
Know the three words: tight means one legal size, loose means minimum zero, unbounded means an infinite maximum.
Name the producers of each class and explain why SizedBox(width: double.infinity) fills a Column but fails in a Row.
Describe a layout bug by the class of each axis as it reaches the widget, and pick the ancestor change that bounds or loosens the right axis.
Set guidance for shared widgets: they must behave under tight, loose and unbounded parents, and reviews should reject components that assume a bounded axis.
## Three words for one object A `BoxConstraints` is four doubles: `minWidth`, `maxWidth`, `minHeight`, `maxHeight`. The words **tight**, **loose** and **unbounded** describe what those numbers look like on each axis. The framework source defines them precisely, and the getters on `BoxConstraints` let you test them in code. | Term | Definition on an axis | Getter | Typical producer | |---|---|---|---| | Tight | min equals max | `hasTightWidth`, `isTight` (both axes) | the root view, `SizedBox.expand` inside a bounded parent, a vertical list's cross axis | | Loose | min is 0 | (check `minWidth == 0`) | `Center`, `Align`, `BoxConstraints.loose(size)` | | Bounded | max is finite | `hasBoundedWidth` | most parents on screen | | Unbounded | max is `double.infinity` | `!hasBoundedWidth` | `Row`/`Column` main axis for non-flex children, scroll views along the scroll axis, `UnconstrainedBox` | | Infinite | min is `double.infinity` | `hasInfiniteWidth` | a mistake: an infinite size cannot be rendered | Two details from the source are worth quoting in an interview: - An axis with min 0 and max 0 is **both tight and loose** at once. - An axis whose min and max are both infinite is called **expanding**; `BoxConstraints.expand()` builds it, and it is only usable as a request that a parent's range will clamp. ## Tight constraints A tight axis offers exactly one size. The root of a full-screen app receives tight constraints equal to the view, which is why the top widget always fills the screen. Any box that passes its tight constraints through unchanged (padding deflates them but keeps them tight) forces the next child to one size too. A vertical `ListView` lays out its box children with a tight **cross-axis** width equal to its own width, so list items always span the list. ## Loose constraints A loose axis has a zero minimum and some maximum. `Center` and `Align` exist mainly to **loosen**: they call `constraints.loosen()`, which keeps the maximums and drops the minimums, and then position the child they let be smaller. `BoxConstraints.loose(Size(300, 600))` builds 0-300 by 0-600 directly. ## Unbounded constraints An unbounded axis has `maxWidth` or `maxHeight` equal to `double.infinity`. It is how a parent says 'take whatever you need on this axis'. The common producers: 1. `Row` and `Column` lay out non-flexible children with an unbounded main axis (width for `Row`, height for `Column`), then add up the sizes. 2. Scrollable views lay out their content with an unbounded extent along the scroll axis. 3. `UnconstrainedBox` passes `BoxConstraints()` — zero to infinity on both axes, or on one axis with `constrainedAxis`. Unbounded constraints are fine for widgets that have a natural size (text, icons, fixed boxes). They break widgets that try to be **as big as possible**, because 'as big as possible' is infinite. The framework reports those cases as errors in debug mode rather than trying to paint an infinite box. ## The infinite-size error The clearest example is a box that asks to fill its parent while the parent has no limit: - `SizedBox(width: double.infinity)` builds `tightFor(width: double.infinity)`: min and max width infinite. - Under a bounded parent (say 0-360), `enforce()` clamps infinity to 360: the box fills the parent. That is why `double.infinity` is a common 'full width' idiom inside a `Column`. - Under an unbounded parent (0 to infinity, a `Row` main axis or `UnconstrainedBox`), clamping leaves the minimum infinite. The constraint check fails with **'BoxConstraints forces an infinite width.'** and nothing renders. The fix is to give that axis a bound: move the widget to a parent that bounds it, give it a finite size, or give it a flex factor inside the `Row` (the flex leaf covers that mechanism). `LimitedBox` is the tool for a reusable widget that must behave sensibly in both worlds, because it caps only an unbounded axis. ## Reading them together When a layout surprises you, write down each axis's class as it arrives at the widget, for example 'width tight 360, height unbounded'. That single line usually predicts the result: tight wins over any request, loose lets the request through up to the max, and unbounded lets natural sizes through but turns 'fill' into an error.
- In Flutter, how can one BoxConstraints be tight on one axis and loose on the other?Each axis is judged on its own min and max. `BoxConstraints.tightFor(width: 300)` gives min and max width 300 (tight) with height 0 to infinity (loose and unbounded). A vertical list's items are the everyday example: tight cross-axis width, unbounded height along the scroll axis.
- Why does an unbounded axis not break Text or Icon the way it breaks SizedBox(width: double.infinity)?Text and Icon have a natural size: they measure their content and report it, which is finite regardless of the maximum. The failure happens only for widgets that try to fill their maximum or force an infinite minimum, because an infinite size cannot be laid out or painted.
saying these in an interview costs you the question
- Loose means the maximum is infinite.
- A Row gives each non-flexible child the Row's full width as a bound.
- Unbounded constraints are themselves an error in Flutter.
- double.infinity as a width always throws an error.
- Tight constraints mean the widget has no children.