# How to Create Conditional Configurations in Hypster: A Complete Guide

> Learn to create conditional configurations in Hypster using Python conditionals and hp.select() to build dynamic hyperparameter search spaces. Master dependency handling.

- Repository: [Gilad Rubin/hypster](https://github.com/gilad-rubin/hypster)
- Tags: how-to-guide
- Published: 2026-03-02

---

**To create conditional configurations in Hypster that depend on other parameter values, use standard Python conditionals (`if`, `elif`, `else`) inside your configuration function after defining a controlling parameter with `hp.select()`.**

Hypster treats configuration functions as ordinary Python code, enabling dynamic branching based on parameter values. The `gilad-rubin/hypster` library provides a flexible API through the `HP` class that resolves values at runtime, allowing you to build complex configuration spaces where subsequent parameters depend on earlier choices.

## How Conditional Logic Works in Configuration Functions

Unlike rigid configuration DSLs, Hypster executes your configuration function as native Python. The `hp: HP` argument provides methods like `hp.select()`, `hp.int()`, and `hp.nest()`, but the control flow remains pure Python.

When you call `hp.select()`, the method returns the actual value (either the default or an override) immediately. This allows you to use the result in standard conditional statements. The selector implementation resides in `HP._handle_select_single()` at **[src/hypster/hp.py#L190-L207](https://github.com/gilad-rubin/hypster/blob/master/src/hypster/hp.py#L190-L207)**.

## Step-by-Step Implementation

### Define the Controlling Parameter

First, establish the parameter that determines your configuration branch. Use `hp.select()` to define discrete options:

```python
def config(hp: HP):
    model_type = hp.select(["gpt", "claude"], name="model_type", default="gpt")
    # model_type now holds the string "gpt" or "claude"

```

The `HP` class validates the value against allowed options and checks for duplicate parameter names using the internal `called_params` set.

### Branch Using Python Conditionals

With the controlling value captured, use standard Python conditionals to define branch-specific parameters:

```python
    if model_type == "gpt":
        top_p = hp.float(0.9, name="top_p", min=0.0, max=1.0)
        return {"model": "gpt", "top_p": top_p}
    else:
        temperature = hp.float(0.7, name="temperature", min=0.0, max=1.0)
        return {"model": "claude", "temperature": temperature}

```

For conditional sub-configurations, use `hp.nest()`. This method creates a fresh `HP` instance for the child configuration, filters overrides belonging to that branch, and maintains duplicate-parameter detection across the entire call graph. The nesting logic is implemented at **[src/hypster/hp.py#L335-L389](https://github.com/gilad-rubin/hypster/blob/master/src/hypster/hp.py#L335-L389)**.

### Handle Nested Overrides and Validation

When using `hp.nest()`, the system automatically resolves dotted keys (e.g., `"model.param"`) and nested dictionaries. According to the test suite in [`tests/test_nested_dict_override_precedence.py`](https://github.com/gilad-rubin/hypster/blob/main/tests/test_nested_dict_override_precedence.py), nested dictionary overrides take precedence over dotted keys, ensuring predictable configuration merging.

The `HP` class also utilizes `unflatten_dict` from **[src/hypster/utils.py](https://github.com/gilad-rubin/hypster/blob/master/src/hypster/utils.py)** to handle complex nested override structures.

## Practical Code Examples

### Simple Conditional Parameter Selection

This example demonstrates conditional parameter definition based on model family selection:

```python
from hypster import HP, instantiate

def config(hp: HP):
    # Choose the model family

    model_type = hp.select(["gpt", "claude"], name="model_type", default="gpt")

    # Conditional hyper-parameters

    if model_type == "gpt":
        top_p = hp.float(0.9, name="top_p", min=0.0, max=1.0)
        return {"model": "gpt", "top_p": top_p}
    else:
        temperature = hp.float(0.7, name="temperature", min=0.0, max=1.0)
        return {"model": "claude", "temperature": temperature}

# Default run – uses the "gpt" branch

print(instantiate(config))

# → {'model': 'gpt', 'top_p': 0.9}

# Override to the "claude" branch

print(instantiate(config, values={"model_type": "claude", "temperature": 0.5}))

# → {'model': 'claude', 'temperature': 0.5}

```

### Conditional Nesting with Sub-Configurations

For complex scenarios, conditionally nest entire configuration functions:

```python
from typing import Dict, Any
from hypster import HP, instantiate

def model_a(hp: HP) -> Dict[str, Any]:
    return {"type": "A", "param": hp.int(1, name="param")}

def model_b(hp: HP) -> Dict[str, Any]:
    return {"type": "B", "param": hp.float(2.0, name="param")}

def config(hp: HP) -> Dict[str, Any]:
    # Controlling selector

    model_type = hp.select(["a", "b"], name="model_type", default="a")

    # Conditional nesting

    if model_type == "a":
        model = hp.nest(model_a, name="model")
    else:
        model = hp.nest(model_b, name="model")

    return {"model": model}

# Default – selects model_a

print(instantiate(config))

# → {'model': {'type': 'A', 'param': 1}}

# Force model_b and override its nested parameter

print(instantiate(config, values={"model_type": "b", "model.param": 3.5}))

# → {'model': {'type': 'B', 'param': 3.5}}

```

The `test_conditional_nesting` test case in [`tests/test_nesting.py`](https://github.com/gilad-rubin/hypster/blob/main/tests/test_nesting.py) validates this pattern.

### Nested Dictionary Override Precedence

When overriding nested configurations, understand the precedence rules:

```python
def child(hp: HP):
    return {"x": hp.int(10, name="x"), "y": hp.int(20, name="y")}

def parent(hp: HP):
    return hp.nest(child, name="child")

# Both dotted and nested dict overrides – nested dict wins

result = instantiate(
    parent,
    values={
        "child.x": 100,          # dotted

        "child": {"x": 200},     # nested dict

    },
)
print(result)   # → {'x': 200, 'y': 20}

```

## Summary

- **Use plain Python**: Hypster configuration functions execute as standard Python, supporting `if`, `elif`, `else`, and loops for conditional logic.
- **Capture values first**: Call `hp.select()` to get the actual value before conditional branches, enabling dynamic parameter definition.
- **Nest conditionally**: Use `hp.nest()` within conditional blocks to load sub-configurations based on runtime values, with automatic override filtering.
- **Override handling**: The system supports both dotted notation (`"model.param"`) and nested dictionaries, with nested dicts taking precedence.
- **Validation across branches**: The `HP` class tracks `called_params` to prevent duplicate definitions even across mutually exclusive conditional branches.

## Frequently Asked Questions

### Can I use loops to generate conditional parameters in Hypster?

Yes, because Hypster executes configuration functions as ordinary Python, you can use `for` and `while` loops to dynamically generate parameters based on other values. For example, you could iterate over a list returned by `hp.select()` to create multiple related parameters. The `HP` API simply provides methods to register hyperparameters within this standard Python execution context.

### How does Hypster handle overrides for parameters in unselected branches?

Parameters defined inside branches not taken during execution are never evaluated, so no overrides are required for them. When you call `instantiate()`, Hypster only processes `HP` method calls within the active execution path. This lazy evaluation means you only need to provide overrides for parameters that are actually accessed, reducing configuration complexity.

### What happens if different branches define parameters with the same name?

Hypster prevents this through the `called_params` tracking mechanism. If two branches both attempt to define a parameter with identical names (e.g., `"param"`), the `HP` class raises an error because the set tracks all called parameters across the entire function execution, regardless of branching. This ensures namespace consistency in your configuration space.

### Can I nest multiple levels of conditional configurations?

Absolutely. You can chain `hp.nest()` calls within conditional blocks to arbitrary depths. Each nested call creates a fresh `HP` instance that inherits the parent's override context but filters for its specific namespace. As implemented in `HP.nest()` at lines 335-389, the method properly handles dotted keys like `"parent.child.param"` and maintains the `called_params` set across the entire nesting hierarchy to prevent duplicates at any level.