cswork / libraryOpen practice workspace

Articles

Azure Bicep Interview Questions: Modules, Deployment Scope, and What-If Limits

Practice Azure Bicep interview answers with cross-scope modules, existing resources, and a what-if preview that cannot evaluate every change.

“A subscription-level Bicep deployment must create a resource group, deploy a storage account inside it, and reference an existing network in another group. The what-if output looks mostly harmless. Would you approve it?” A strong interview answer checks three separate things: the deployment scope of each declaration, what the preview actually evaluated, and which changes remain uncertain. Bicep syntax alone does not make a rollout safe.

This guide works through an original two-scope manifest and a deliberately incomplete what-if report. Evidence boundary: Microsoft Learn supports the Bicep module, scope, existing, and what-if behavior below. The manifest, approval decision, and test plan are editorial exercises. No candidate report is used for a specific interview-process claim.

Start with the deployment's target scope

Bicep files declare a targetScope, such as resourceGroup, subscription, managementGroup, or tenant. In this exercise, the parent file targets a subscription. Microsoft's subscription-scope guidance says ordinary resource declarations in that file must deploy at the deployment scope, with documented child and extension-resource cases. To deploy an ordinary resource in a resource group, use a module whose scope is that group. The module is a nested deployment, so the caller needs permission at the destination scope as well as permission for the parent operation.

That is the first trap: a storage account is a resource-group resource. Declaring it directly in a subscription-scoped parent and assuming a resourceGroup() expression relocates it is not a cross-scope deployment plan. Put the account in a resource-group-scoped module. A module without an explicit scope inherits the parent's target scope; if the interviewer removes scope from the example, explain why the module now targets the wrong place rather than guessing that its filename decides its scope.

existing is different from create. Microsoft's docs allow an existing declaration to reference a resource outside the current deployment scope. It gives a symbolic reference and can expose properties or IDs; it does not deploy that resource. The source group and resource name must be correct, and the deployment identity needs whatever read or deployment permissions the actual expression requires. Treat an existing declaration as an assumption to verify, not as proof that the target exists or belongs to this environment.

Worked fixture: two scopes and one external dependency

Suppose the desired state is a group rg-app-west, a storage account inside it, and an output pointing to an already-created virtual network in rg-network-west. The subscription-level parent could follow this shape:

targetScope = 'subscription'

param location string
param appGroupName string
param networkGroupName string
param networkName string

resource appGroup 'Microsoft.Resources/resourceGroups@2025-04-01' = {
  name: appGroupName
  location: location
}

resource network 'Microsoft.Network/virtualNetworks@2024-05-01' existing = {
  name: networkName
  scope: resourceGroup(networkGroupName)
}

module storage './storage.bicep' = {
  name: 'storage-${uniqueString(appGroup.id)}'
  scope: appGroup
  params: {
    location: location
  }
}

output existingNetworkId string = network.id
output storageAccountId string = storage.outputs.accountId

The child file would target resourceGroup and create the storage account there. This is illustrative code: the exact resource API version, storage name, network scope, permission model, and module parameters need validation against the actual tenant. The useful reasoning is structural. The parent owns the group and references the network; the child owns the account. scope: appGroup ties the child to the new group's symbolic identity and makes the dependency intelligible. storage.outputs.accountId is an output from the nested deployment, not a way to bypass its scope.

The module name is a deployment identity. Microsoft's module guidance warns that simultaneous deployments using the same static module name at the same scope can interfere with output retrieval. A deterministic name derived from a stable resource ID helps separate this instance, but it is not a full concurrency policy if two updates to the same environment run together. Prevent overlapping production deployments or coordinate them deliberately; do not make the name random just to hide concurrent write conflicts.

Now change one variable: networkGroupName points to a group in a different subscription. The simple resourceGroup(networkGroupName) reference is no longer a sufficient statement of scope. Supply the correct subscription and group in the scope expression, verify permissions, and inspect whether the referenced network is truly the intended one. A candidate who says “existing means Bicep will create it if missing” has crossed the key evidence boundary.

Read what-if as a prediction with blind spots

Azure Resource Manager's what-if operation previews changes without modifying existing resources. It is useful for detecting unplanned creates, modifications, and deletes, but Microsoft documents several limits. The service can short-circuit analysis when a resource ID or API version cannot be calculated outside deployment context; a module can be excluded from detailed analysis. Once nested-template expansion limits are reached, remaining resources may be labeled Ignore. Template links inside nested deployments also are not fully expanded for what-if. A green-looking report is therefore only as complete as its diagnostics and coverage.

For the fixture, imagine a report that clearly lists the group create, marks the storage module as not fully evaluated, and shows a property change on the network reference. Do not interpret the blank storage section as “storage unchanged.” Look for the diagnostic explaining what was skipped, compare the compiled template and child module inputs, and resolve the expression or deployment structure if possible. If the uncertainty cannot be removed, use a staging deployment or a narrower targeted preview, then require an explicit human review of the unresolved part before production.

Ignore is particularly easy to misuse. Microsoft's what-if documentation describes it in more than one context, including resources outside the template and resources left unanalyzed after expansion limits. Read the diagnostic and scope, not the label in isolation. A change type is not an assurance that a missing resource will be preserved under every deployment mode or stack operation. State the actual deployment mode and tooling before discussing deletion risk.

What-if also has permission requirements comparable to a deployment, including access to deployment operations and writes on resources being targeted. A failed preview because of authorization is not a zero-change result. Keep the CLI/PowerShell tool version in the record because Microsoft notes newer versions show diagnostics for some short-circuit cases that older clients could hide. A reviewer should know whether the report was complete, partial, or failed.

A small approval ledger beats a screenshot

The original exercise uses a four-row ledger to turn the preview into a decision:

ObjectIntended statePreview evidenceRelease decision
rg-app-westNew groupExplicit create shownCheck subscription, location, and name.
Storage moduleNew account in app groupModule not fully evaluatedHold until child changes are inspected or staged.
Existing networkReference onlyID/scope in compiled templateVerify subscription, group, and identity; no creation expected.
Deployment identityUnique for this environmentModule name in parentPrevent overlapping runs and confirm output mapping.

This table is not an Azure-generated report; it is a review artifact. It makes uncertainty visible and assigns a next action instead of declaring “what-if passed.” The interviewer can change one line—storage module scope or network subscription—and ask you to update the ledger. That exercise is more revealing than memorizing a list of Bicep keywords.

If a property change is due to utcNow(), a secure parameter, listKeys(), or a reference to a resource outside the template, Microsoft says the expression may not be evaluated during what-if. Treat that diff as a prediction with noise rather than blindly accepting or rejecting it. Ask whether the value is operationally consequential, inspect the resource state and template intent, and deploy in a controlled environment when necessary. Avoid outputting secrets merely to make the preview “more complete.”

What would you test before a production deployment?

First, compile and validate the Bicep files so scope/type errors appear early. Inspect the compiled structure and module inputs for the exact environment. Second, run what-if at the correct parent scope with the actual parameters and capture diagnostics, tool version, deployment mode, and identity. Third, compare the expected resource IDs to live inventory, especially the existing network and cross-scope module target. Finally, stage the change, verify the created account and outputs, and monitor the production deployment operation and final resource state.

The test should catch a wrong subscription even if the file compiles. Use explicit environment parameters rather than relying on whichever subscription the CLI happens to select. Protect the production identity with least-privilege access; what-if and deploy both need sufficient scope rights. If a group is created in the same template, a module scoped to the group's symbolic name captures that relationship more clearly than a manually assembled string.

After deployment, read the actual group and account IDs and compare them to the intended ledger. A successful deployment status is necessary but not sufficient if it created a valid account in the wrong environment. Check tags, networking, access controls, and any module outputs the application consumes. For rollback, identify whether the change is reversible by redeployment, whether stateful data lives in the resource, and whether an old template would delete or mutate anything. Infrastructure rollback is not a synonym for restoring data.

Fast answers to common interview follow-ups

What does a module do? It packages a Bicep or ARM template as a nested deployment, can accept parameters and emit outputs, and can target a different scope with scope. Its name is the nested deployment's name, so consider collisions when multiple operations run at the same destination.

What does existing do? It references a resource Bicep will not create in this declaration. Verify its actual scope and identity. Accessing its runtime properties may add a dependency or permission requirement.

Can a subscription file create a storage account directly in a group? Use a resource-group-scoped module for an ordinary group resource. The parent targetScope does not silently switch when you write a group name.

Does what-if guarantee no surprise changes? No. It does not mutate resources, but predictions may be partial or noisy. Read short-circuit diagnostics, unresolved expressions, template-link limitations, and expansion limits before approval.

How do you handle a partial what-if? Inventory the skipped module, inspect its compiled template and inputs, narrow or stage the deployment, and document remaining uncertainty. Do not promote a blank section as a clean result.

Sources and further reading