skip to content

AGP Build Variants and Flavors

Build types, product flavors, and the variant matrix they produce — all AGP concepts rather than core Gradle. Asked because the variant explosion is the first thing that surprises people moving to Android builds.

on this pageshow

questions

5

What are build types in the Android Gradle Plugin (AGP), and what do the default `debug` and `release` build types represent?

level: juniorimportance: must knowfreq 70%

answer

  1. how it's built, not what's in it
  2. debug vs release defaults
  3. minifyEnabled / shrinkResources on release
  4. initWith to copy
  5. per-type source set + assemble task

basics

~10 s

Build types are AGP configurations for how an app is built. AGP ships two by default: debug (debuggable, no shrinking) and release (optimizable, shrinkable, signable for distribution).

solid answer

~40 s

A **build type** in AGP describes *how* the same source is packaged, not *what* code it contains. AGP provides two out of the box: `debug` and `release`. `debug` sets `debuggable true`, applies a debug signing config automatically, and disables shrinking so iteration is fast. `release` defaults `debuggable false` and is where you enable `minifyEnabled`, `shrinkResources`, and a real signing config for the Play Store. You configure them in the `android { buildTypes { ... } }` block, and you can add custom ones like `staging` that inherit from a base with `initWith`. Each build type contributes a Gradle source set (`src/debug`, `src/release`) and generates tasks such as `assembleDebug` / `assembleRelease`. Build types are an AGP concept layered on top of Gradle's source-set and task model — Gradle core has no notion of them.

code

kotlin · 16 lines
kotlin
android {
  buildTypes {
    getByName("debug") {
      applicationIdSuffix = ".debug"
      isDebuggable = true
    }
    getByName("release") {
      isMinifyEnabled = true
      isShrinkResources = true
      proguardFiles(
        getDefaultProguardFile("proguard-android-optimize.txt"),
        "proguard-rules.pro"
      )
    }
  }
}

go deeper

for a junior

Name the two defaults and that debug = fast iteration, release = shrinkable/signable for the store.

for a middle

Explain the configuration block, custom build types, initWith, applicationIdSuffix, and the generated source sets/assemble tasks.

for a senior

Contrast build types vs flavors as orthogonal axes, discuss signing configs, R8/minify trade-offs, and a staging type strategy.

for a principal

Frame build-type policy across an org: signing-key governance, ensuring release-only hardening (minify, no debuggable), and that these are AGP abstractions over Gradle source sets.

## What a build type is The Android Gradle Plugin (AGP) is an *ecosystem plugin* that extends Gradle with Android-specific build logic via the `android { }` extension. A **build type** is one of two orthogonal axes AGP adds on top of Gradle's source-set/task model. A build type answers the question: *how* is this app assembled and packaged? It does **not** change which features/code the app contains — that is the job of *product flavors* (the other axis). ## The two defaults AGP registers two build types automatically: - **`debug`** — `debuggable true`, an auto-generated debug signing config (so it installs on a device with zero setup), shrinking/obfuscation off. Optimized for fast inner-loop development. - **`release`** — `debuggable false`. This is where you turn on `minifyEnabled true` (R8 code shrinking + obfuscation), `shrinkResources true` (strip unused resources), and attach a **real** signing config with your upload/release key for the Play Store. ## How you configure them They live in `android { buildTypes { ... } }`. You can add custom build types and seed them from an existing one with `initWith`: ```kotlin android { buildTypes { release { isMinifyEnabled = true isShrinkResources = true proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") } create("staging") { initWith(getByName("release")) // copy release settings applicationIdSuffix = ".staging" isDebuggable = true } } } ``` ## What each build type buys you mechanically - A dedicated **source set**: `src/debug/`, `src/release/`, `src/staging/` (Java/Kotlin, resources, manifest) merged on top of `src/main/`. - Generated **tasks**: `assembleDebug`, `assembleRelease`, `assembleStaging`, plus install/test variants. - A field in `BuildConfig` and the ability to set `buildConfigField`, `manifestPlaceholders`, `applicationIdSuffix`, `versionNameSuffix` per type. ## Relationship to Gradle core Build types are purely an AGP abstraction. Under the hood AGP wires them into Gradle's source sets, configurations, and a generated task graph — but if you opened a plain `java` project there would be no `buildTypes` block. That distinction matters in interviews: build types/flavors/variants are *AGP*, while configurations, tasks, and the dependency graph are *Gradle core*.

  • Why must you never ship a build with `debuggable true` to production?
    It disables certain optimizations, leaves the app open to attaching a debugger and inspecting/altering runtime state, and Play rejects debuggable release uploads. It also typically skips R8 shrinking/obfuscation.
  • What does `initWith` do?
    It copies all properties from an existing build type into a new one as a starting baseline, which you then override — useful for a `staging` type derived from `release`.

A build type is like choosing how to print the same manuscript: a cheap fast draft (debug) versus the bound, proofed final edition (release). Same words, different packaging.

saying these in an interview costs you the question

  • Saying build types decide *what features* the app has — that's product flavors
  • Claiming build types are a Gradle core feature rather than AGP
  • Thinking `debug` is signed with your release key

context

open as a page

What are product flavors in AGP, and how do flavor dimensions let you combine them?

level: middleimportance: must knowfreq 60%

basics

~10 s

Product flavors define what version of an app you build (e.g. free vs paid). Flavor dimensions group flavors so AGP combines one flavor from each dimension into the final app.

open as a page

How does AGP compute the build variant matrix from build types and product flavors, and what tasks/source sets does each variant produce?

level: middleimportance: must knowfreq 55%

basics

~20 s

A variant is one combination: (one flavor per dimension) × (one build type). AGP multiplies them — e.g. 2 flavors × 2 build types = 4 variants — and generates a source set and assemble<Variant> task for each.

open as a page

A team's AGP build has grown to dozens of variants and CI is slow. How do you reason about and control the variant explosion?

level: seniorimportance: should knowfreq 30%

basics

~10 s

Variants multiply (flavors-per-dimension × build types), so they explode fast. Prune impossible combinations with beforeVariants/variantFilter, reduce dimensions, and have CI build only the variants it actually needs.

open as a page

How do AGP build types, flavors, and variants relate to Gradle core concepts like source sets, configurations, and tasks?

level: seniorimportance: should knowfreq 40%

basics

~10 s

Build types, flavors, and variants are AGP-only abstractions. AGP implements them by generating Gradle source sets, dependency configurations (like <variant>Implementation), and tasks (assemble<Variant>) — Gradle core itself has no variant concept.

open as a page