Schema Design Practice Questions

13 minute read

Practice Questions

Test your understanding of schema design concepts with these practice questions. Try to answer each question before looking at the solution.

Question 1: Attribute Configuration

Which attribute configuration is invalid?

A) Required: true
B) Optional: true, Computed: true
C) Required: true, Optional: true
D) Computed: true

Show Answer **Answer: C** `Required: true, Optional: true` is invalid because an attribute cannot be both required and optional. **Valid combinations:** - `Required: true` - `Optional: true` - `Computed: true` - `Optional: true, Computed: true` - `Required: true, Computed: true` (rare)

Question 2: Naming Convention

Which attribute name follows Terraform provider conventions?

A) instanceType
B) InstanceType
C) instance_type
D) instance-type

Show Answer **Answer: C** `instance_type` uses **snake_case**, which is the standard naming convention for Terraform attributes. **Conventions:** - ✅ Use snake_case: `instance_type`, `max_retries` - ❌ Avoid camelCase: `instanceType` - ❌ Avoid PascalCase: `InstanceType` - ❌ Avoid kebab-case: `instance-type`

Question 3: Validation Timing

When should validation occur?

A) During terraform apply
B) During terraform plan
C) During resource Create function
D) During resource Update function

Show Answer **Answer: B** Validation should occur during `terraform plan` (before apply). **Why?** - Early error detection - Fast feedback - No API calls wasted - Better user experience **How?** ```go "port": schema.NumberAttribute{ Required: true, Validators: []validator.Number{ numbervalidator.Between(1, 65535), // Validated at plan time }, }, ```

Question 4: Sensitive Attributes

What does marking an attribute as Sensitive: true do?

A) Encrypts the value in state file
B) Hides the value from plan/apply output
C) Prevents the value from being stored in state
D) Requires the value to be encrypted before sending to provider

Show Answer **Answer: B** `Sensitive: true` **hides the value from plan/apply output** but does NOT encrypt it in state. **What it does:** - ✅ Hides from `terraform plan` output - ✅ Hides from `terraform apply` output - ✅ Hides from `terraform show` output **What it does NOT do:** - ❌ Does NOT encrypt in state file - ❌ Does NOT prevent storage in state - ❌ Does NOT encrypt during transmission **State file still contains plaintext:** ```json { "password": "super-secret" // Still plaintext! } ```

Question 5: Nested Attribute Types

Which nested attribute type should you use for multiple unordered, unique items?

A) SingleNestedAttribute
B) ListNestedAttribute
C) SetNestedAttribute
D) MapNestedAttribute

Show Answer **Answer: C** `SetNestedAttribute` for **unordered, unique** items. **Nested attribute types:** - **SingleNestedAttribute**: One occurrence - **ListNestedAttribute**: Multiple ordered occurrences - **SetNestedAttribute**: Multiple **unordered, unique** occurrences - **MapNestedAttribute**: Key-value nested structures **Example:** ```go "tag": schema.SetNestedAttribute{ // Unordered, unique NestedObject: schema.NestedAttributeObject{ Attributes: map[string]schema.Attribute{ "key": schema.StringAttribute{Required: true}, "value": schema.StringAttribute{Required: true}, }, }, } ```

Question 6: Default Values

How do you provide a default value for an optional attribute?

A) Set it in the Create function
B) Use Default modifier in schema
C) Use ModifyPlan function
D) Both B and C are correct

Show Answer **Answer: D** Both **Default modifier** and **ModifyPlan** can provide defaults. **Method 1: Default modifier (preferred for static defaults)** ```go "port": schema.NumberAttribute{ Optional: true, Computed: true, Default: numberdefault.StaticNumber(big.NewFloat(8080)), } ``` **Method 2: ModifyPlan (for dynamic defaults)** ```go func (r *MyResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { if plan.Port.IsNull() { plan.Port = types.NumberValue(big.NewFloat(8080)) } resp.Plan.Set(ctx, plan) } ```

Question 7: Backwards Compatibility

Which change is backwards compatible?

A) Removing an optional attribute
B) Changing an attribute type from StringAttribute to NumberAttribute
C) Adding a new optional attribute
D) Changing an optional attribute to required

Show Answer **Answer: C** **Adding a new optional attribute** is backwards compatible. **Backwards compatible changes:** - ✅ Adding optional attributes - ✅ Adding computed attributes - ✅ Making required attributes optional - ✅ Deprecating (not removing) attributes **Breaking changes:** - ❌ Removing attributes - ❌ Changing attribute types - ❌ Making optional attributes required - ❌ Renaming attributes

Question 8: Cross-Attribute Validation

Where should you implement validation that max_instances >= min_instances?

A) In the schema validators
B) In the Create function
C) In the ValidateConfig function
D) In the ModifyPlan function

Show Answer **Answer: C** Cross-attribute validation belongs in the `ValidateConfig` function. **Implementation:** ```go func (r *MyResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) { var config MyResourceModel req.Config.Get(ctx, &config) if !config.MinInstances.IsNull() && !config.MaxInstances.IsNull() { minVal, _ := config.MinInstances.ValueBigFloat().Int64() maxVal, _ := config.MaxInstances.ValueBigFloat().Int64() if minVal > maxVal { resp.Diagnostics.AddError( "Invalid Configuration", fmt.Sprintf("min_instances (%d) must be <= max_instances (%d)", minVal, maxVal), ) } } } ``` **Why ValidateConfig?** - Runs during `terraform plan` - Has access to all attributes - Purpose-built for cross-field validation

Question 9: Computed Attributes

When should an attribute be marked as Computed: true?

A) When the user must provide a value
B) When the provider/API generates the value
C) When the attribute is optional
D) When the attribute is sensitive

Show Answer **Answer: B** Mark as `Computed: true` when the **provider/API generates the value**. **Examples of computed attributes:** ```go // IDs - server-generated "id": schema.StringAttribute{ Computed: true, } // Timestamps - server-generated "created_at": schema.StringAttribute{ Computed: true, } // ARNs - server-generated "arn": schema.StringAttribute{ Computed: true, } // Status - server-determined "status": schema.StringAttribute{ Computed: true, } ``` **Not computed:** ```go // User provides "name": schema.StringAttribute{ Required: true, // Not computed! } ```

Question 10: Validation Error Messages

Which error message is better?

A) “Invalid value”
B) “Port must be between 1 and 65535, got 99999”
C) “Error”
D) “Wrong format”

Show Answer **Answer: B** **"Port must be between 1 and 65535, got 99999"** is the best error message. **Good error messages:** - ✅ Explain the constraint - ✅ Show the actual value - ✅ Actionable and specific - ✅ User-friendly **Poor error messages:** - ❌ "Invalid value" - Too vague - ❌ "Error" - No information - ❌ "Wrong format" - Not specific **Implementation:** ```go numbervalidator.Between(1, 65535) // Framework provides good message // Custom validator with good message stringvalidator.RegexMatches( regexp.MustCompile(`^[a-z][a-z0-9-]*$`), "must start with lowercase letter and contain only lowercase letters, numbers, and hyphens", ) ```

Question 11: Boolean Naming

Which boolean attribute name is best?

A) disabled
B) not_enabled
C) enabled
D) is_not_active

Show Answer **Answer: C** Use **positive** boolean names like `enabled`. **Good boolean names:** - ✅ `enabled` - ✅ `auto_scaling` - ✅ `monitoring` - ✅ `public_access` **Poor boolean names:** - ❌ `disabled` - Negative logic confusing - ❌ `not_enabled` - Double negative - ❌ `is_not_active` - Awkward **Example:** ```go "enabled": schema.BoolAttribute{ Optional: true, Computed: true, Default: booldefault.StaticBool(true), } ``` ```hcl # Clear intent enabled = true # Enable enabled = false # Disable # vs confusing disabled = false # Wait, is this enabled? ```

Question 12: Nesting Depth

What is the recommended maximum nesting depth for schemas?

A) 1 level
B) 2-3 levels
C) 5 levels
D) Unlimited

Show Answer **Answer: B** Keep nesting to **2-3 levels maximum**. **Good (2-3 levels):** ```hcl deployment { # Level 1 rolling_update { # Level 2 max_surge = 1 } } ``` **Too deep (5+ levels):** ```hcl level1 { level2 { level3 { level4 { level5 { # Too deep! value = "help" } } } } } ``` **Why limit depth?** - Improves readability - Easier to understand - Simpler validation - Better user experience

Question 13: Deprecation

How do you deprecate an attribute without breaking existing configurations?

A) Remove it from the schema
B) Set DeprecationMessage
C) Change it to Computed: true
D) Rename it

Show Answer **Answer: B** Use `DeprecationMessage` to deprecate without breaking. **Implementation:** ```go "instance_type": schema.StringAttribute{ Optional: true, DeprecationMessage: "Use 'instance_class' instead. This attribute will be removed in v3.0", }, "instance_class": schema.StringAttribute{ Optional: true, }, ``` **Deprecation lifecycle:** 1. **v1.0**: `instance_type` only 2. **v2.0**: Add `instance_class`, deprecate `instance_type` 3. **v3.0**: Remove `instance_type` (major version) **User sees:** ``` Warning: Attribute Deprecated The attribute "instance_type" is deprecated. Use 'instance_class' instead. This attribute will be removed in v3.0 ```

Question 14: Null vs Empty

What’s the difference between types.StringNull() and types.StringValue("")?

A) No difference
B) Null means not provided, empty string means user provided “”
C) Null is for optional, empty is for required
D) Null is invalid, empty is valid

Show Answer **Answer: B** **Null means not provided**, empty string means **user explicitly provided ""**. **Example:** ```go // User didn't specify types.StringNull() // Not in config // User specified empty string types.StringValue("") // In config: value = "" ``` **Why it matters:** ```go if plan.Description.IsNull() { // User didn't provide description - use default plan.Description = types.StringValue("Default description") } else if plan.Description.ValueString() == "" { // User explicitly set description = "" // Respect their choice, don't apply default } ``` **Similar for other types:** ```go types.NumberNull() // Not provided types.NumberValue(big.NewFloat(0)) // User provided 0 types.BoolNull() // Not provided types.BoolValue(false) // User provided false types.ListNull(types.StringType) // Not provided types.ListValue(types.StringType, []attr.Value{}) // User provided [] ```

Question 15: Required Fields

Which attribute should be required?

A) description - Optional description
B) region - Has default “us-east-1”
C) tags - Optional metadata
D) name - Resource identifier

Show Answer **Answer: D** `name` should be **required** as it's essential for identifying the resource. **Required attributes:** - ✅ Essential identifiers (name) - ✅ Cannot have meaningful defaults - ✅ Must be specified for resource to make sense **Optional attributes:** - ✅ Can have defaults - ✅ Nice-to-have, not essential - ✅ Can be omitted without issues **Example:** ```go // Required - can't create without name "name": schema.StringAttribute{ Required: true, }, // Optional with default "region": schema.StringAttribute{ Optional: true, Computed: true, Default: stringdefault.StaticString("us-east-1"), }, // Optional - not essential "description": schema.StringAttribute{ Optional: true, }, "tags": schema.MapAttribute{ Optional: true, }, ``` **Best practice:** Minimize required fields for better UX.

Question 16: Validation Location

Where should you validate that a port number is between 1-65535?

A) In the Create function
B) In the schema using validators
C) In the ValidateConfig function
D) In the Update function

Show Answer **Answer: B** Use **schema validators** for single-attribute validation. **Implementation:** ```go "port": schema.NumberAttribute{ Required: true, Validators: []validator.Number{ numbervalidator.Between(1, 65535), // ✅ Schema validation }, }, ``` **Why schema validation?** - ✅ Validates during `terraform plan` - ✅ Early error detection - ✅ Built-in validators available - ✅ No API calls wasted **When to use ValidateConfig:** - Cross-attribute validation - Complex business logic - Requires multiple attributes **When NOT to validate:** ```go // ❌ Don't validate in Create/Update (too late!) func (r *MyResource) Create(...) { if port < 1 || port > 65535 { return errors.New("invalid port") // Too late! } } ```

Question 17: Attribute Organization

Which organization is better?

Option A:

Attributes: map[string]schema.Attribute{
    "name": ...,
    "vpc_id": ...,
    "subnet_id": ...,
    "sg_id": ...,
}

Option B:

Attributes: map[string]schema.Attribute{
    "name": ...,
    "network": schema.SingleNestedAttribute{
        Attributes: map[string]schema.Attribute{
            "vpc_id": ...,
            "subnet_id": ...,
            "security_group_id": ...,
        },
    },
}
Show Answer **Answer: Option B** **Group related attributes** in nested blocks for better organization. **Benefits of grouping:** - ✅ Logical organization - ✅ Clear relationships - ✅ Easier to understand - ✅ Better documentation - ✅ Optional blocks **Good organization:** ```go Attributes: map[string]schema.Attribute{ // Identity "id": ..., "name": ..., // Network configuration "network": schema.SingleNestedAttribute{...}, // Deployment configuration "deployment": schema.SingleNestedAttribute{...}, // Computed metadata "arn": ..., "created_at": ..., } ``` **Poor organization:** ```go Attributes: map[string]schema.Attribute{ "name": ..., "arn": ..., "network_vpc": ..., "deployment_type": ..., "network_subnet": ..., "created_at": ..., // Scattered, hard to understand } ```

Question 18: State Management

When should computed attributes be updated?

A) Only in Create
B) Only in Read
C) In Create and Read
D) Never - they’re computed

Show Answer **Answer: C** Update computed attributes in **both Create and Read**. **Create:** ```go func (r *MyResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { // Create resource resource, err := r.client.CreateResource(...) // Set computed attributes from API response plan.ID = types.StringValue(resource.ID) plan.ARN = types.StringValue(resource.ARN) plan.Endpoint = types.StringValue(resource.Endpoint) plan.Status = types.StringValue(resource.Status) plan.CreatedAt = types.StringValue(resource.CreatedAt) resp.State.Set(ctx, plan) } ``` **Read:** ```go func (r *MyResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { // Fetch current state resource, err := r.client.GetResource(...) // Update computed attributes (may have changed) state.Status = types.StringValue(resource.Status) state.Endpoint = types.StringValue(resource.Endpoint) state.UpdatedAt = types.StringValue(resource.UpdatedAt) resp.State.Set(ctx, state) } ``` **Why both?** - **Create**: Initial values from API - **Read**: Updates for drift detection

Question 19: Map vs List for Tags

Which is better for implementing tags?

A) MapAttribute with string values
B) ListNestedAttribute with key/value objects
C) SetNestedAttribute with key/value objects
D) Multiple separate string attributes

Show Answer **Answer: A** Use **MapAttribute** for tags (key-value pairs). **Best choice:** ```go "tags": schema.MapAttribute{ ElementType: types.StringType, Optional: true, MarkdownDescription: "Key-value tags", } ``` ```hcl tags = { Environment = "Production" Team = "Platform" Cost Center = "Engineering" } ``` **Alternative (more verbose):** ```go "tag": schema.SetNestedAttribute{ NestedObject: schema.NestedAttributeObject{ Attributes: map[string]schema.Attribute{ "key": schema.StringAttribute{Required: true}, "value": schema.StringAttribute{Required: true}, }, }, } ``` ```hcl tag { key = "Environment" value = "Production" } tag { key = "Team" value = "Platform" } ``` **Why MapAttribute is better for tags:** - ✅ More concise - ✅ Familiar pattern - ✅ Easier to work with - ✅ Natural key-value representation

Question 20: Documentation

Which attribute description is better?

A) “Timeout value”
B) “Connection timeout in seconds. Must be between 1 and 300. Defaults to 30.”
C) “Timeout”
D) “Seconds for timeout”

Show Answer **Answer: B** **"Connection timeout in seconds. Must be between 1 and 300. Defaults to 30."** is the best description. **Good documentation includes:** - ✅ What the attribute does - ✅ Unit of measurement - ✅ Valid range/values - ✅ Default value - ✅ Any constraints **Example:** ```go "timeout": schema.NumberAttribute{ Optional: true, Computed: true, MarkdownDescription: "Connection timeout in seconds. Must be between 1 and 300. Defaults to 30.", Validators: []validator.Number{ numbervalidator.Between(1, 300), }, Default: numberdefault.StaticNumber(big.NewFloat(30)), } ``` **Poor documentation:** - ❌ "Timeout" - Too vague - ❌ "Timeout value" - No details - ❌ No mention of units, range, or default

Scenario Questions

Scenario 1: Database Password

You’re designing a database resource. Which is the correct schema for a password?

// Option A
"password": schema.StringAttribute{
    Required: true,
}

// Option B
"password": schema.StringAttribute{
    Required:  true,
    Sensitive: true,
    Validators: []validator.String{
        stringvalidator.LengthAtLeast(8),
    },
}

// Option C
"password": schema.StringAttribute{
    Optional:  true,
    Computed:  true,
    Sensitive: true,
}

// Option D
"password": schema.StringAttribute{
    Optional:  true,
    Computed:  true,
    Sensitive: true,
    MarkdownDescription: "Database password. If not provided, a random password will be generated. Must be at least 8 characters.",
    Validators: []validator.String{
        stringvalidator.LengthAtLeast(8),
    },
}
Show Answer **Answer: Option D** **Option D** is best because it: - ✅ `Optional + Computed`: Can be provided or auto-generated - ✅ `Sensitive`: Hidden from logs - ✅ Validated: Minimum length check - ✅ Documented: Clear behavior description **Why not others:** - **Option A**: Not sensitive, no validation, not documented - **Option B**: Must be provided (less flexible), not documented - **Option C**: No validation, not documented **Implementation:** ```go func (r *DatabaseResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { var plan DatabaseModel req.Plan.Get(ctx, &plan) var password string if plan.Password.IsNull() { // Generate random password password = generateSecurePassword() plan.Password = types.StringValue(password) } else { password = plan.Password.ValueString() } // Create database with password // ... } ```

Scenario 2: Instance Count

Which configuration allows 1-10 instances with a default of 2?

// Option A
"instance_count": schema.NumberAttribute{
    Optional: true,
    Validators: []validator.Number{
        numbervalidator.Between(1, 10),
    },
}

// Option B
"instance_count": schema.NumberAttribute{
    Optional: true,
    Computed: true,
    Default:  numberdefault.StaticNumber(big.NewFloat(2)),
    Validators: []validator.Number{
        numbervalidator.Between(1, 10),
    },
}

// Option C
"instance_count": schema.NumberAttribute{
    Required: true,
    Validators: []validator.Number{
        numbervalidator.Between(1, 10),
    },
}
Show Answer **Answer: Option B** **Option B** correctly implements optional with default: - ✅ `Optional`: User can provide or omit - ✅ `Computed`: Can be computed if not provided - ✅ `Default`: Provides default value of 2 - ✅ `Validators`: Enforces 1-10 range **Why not others:** - **Option A**: No default - users must always specify - **Option C**: Required - users must always specify (no default possible) **Usage:** ```hcl # Use default (2) resource "mycloud_app" "default" { name = "app" # instance_count defaults to 2 } # Override default resource "mycloud_app" "custom" { name = "app" instance_count = 5 } ```

Key Takeaways

  • ✅ Use appropriate attribute configurations (Required/Optional/Computed)
  • ✅ Validate early in schema, not in CRUD functions
  • ✅ Mark sensitive data with Sensitive: true
  • ✅ Choose correct nested attribute types for use case
  • ✅ Provide sensible defaults for optional attributes
  • ✅ Maintain backwards compatibility
  • ✅ Write clear, actionable error messages
  • ✅ Document all attributes thoroughly
  • ✅ Follow naming conventions (snake_case, positive booleans)
  • ✅ Group related attributes in nested blocks
  • ✅ Limit nesting depth to 2-3 levels
  • ✅ Test validation rules thoroughly

What’s Next?

Congratulations on completing Module 4! You now understand schema design fundamentals. Next, you’ll learn about provider configuration.

Next Module: Provider Configuration →


Previous: ← Schema Design Lab

Module Home: ← Schema Design Overview

Last updated: June 30, 2026

Updated: