backend / python core / 24_enum_vs_strenum.md

Enum vs StrEnum Differences

3 interview angles 8 min read source

Enum vs StrEnum Differences

Overview

Python’s enum module provides several enumeration types. Enum and StrEnum are two important classes with key differences in how they handle values and string representation.

Basic Definitions

Enum

Enum is the base class for creating enumerated constants. Members can have any type of value.

StrEnum

StrEnum (introduced in Python 3.11) is a specialized Enum where all members must be strings and can be used directly as strings.

Key Differences

Feature Enum StrEnum
Value Type Any type (int, str, tuple, etc.) Only strings
String Usage Requires .value to use as string Can be used directly as string
Type Checking Generic type String-compatible type
Python Version Available since Python 3.4 Available since Python 3.11
Inheritance Base class Inherits from Enum and str

Basic Examples

Enum Example

from enum import Enum

class Color(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3

# Usage
print(Color.RED)        # Color.RED
print(Color.RED.value)  # 1
print(Color.RED.name)   # RED

# Cannot use directly as string
# print("Color is " + Color.RED)  # TypeError!
print("Color is " + Color.RED.name)  # OK: "Color is RED"

StrEnum Example

from enum import StrEnum

class Color(StrEnum):
    RED = "red"
    GREEN = "green"
    BLUE = "blue"

# Usage
print(Color.RED)        # Color.RED
print(Color.RED.value)   # "red"
print(Color.RED.name)   # RED

# Can use directly as string!
print("Color is " + Color.RED)  # OK: "Color is red"
print(f"Color: {Color.RED}")   # OK: "Color: red"

Detailed Comparison

1. Value Assignment

Enum - Can use any type:

from enum import Enum

class Status(Enum):
    PENDING = 1
    PROCESSING = 2
    COMPLETED = 3
    FAILED = "error"  # Mixed types allowed

class Config(Enum):
    TIMEOUT = 30
    RETRIES = 3
    HOSTS = ["host1", "host2"]  # Even lists!

StrEnum - Must use strings:

from enum import StrEnum

class Status(StrEnum):
    PENDING = "pending"
    PROCESSING = "processing"
    COMPLETED = "completed"
    # FAILED = 1  # TypeError: values must be strings

2. String Operations

Enum - Requires explicit conversion:

from enum import Enum

class HTTPMethod(Enum):
    GET = "GET"
    POST = "POST"
    PUT = "PUT"

method = HTTPMethod.GET

# Direct string operations don't work
# if method == "GET":  # False (different types)
#     pass

# Need to use .value or .name
if method.value == "GET":  # OK
    print("GET request")

if method.name == "GET":  # OK
    print("GET request")

# String concatenation requires conversion
message = "Method: " + method.value  # OK

StrEnum - Direct string operations:

from enum import StrEnum

class HTTPMethod(StrEnum):
    GET = "GET"
    POST = "POST"
    PUT = "PUT"

method = HTTPMethod.GET

# Direct string comparison works!
if method == "GET":  # True
    print("GET request")

# Direct string concatenation works!
message = "Method: " + method  # OK: "Method: GET"

# Works in f-strings
print(f"Using {method} method")  # "Using GET method"

3. Type Checking

Enum - Generic type:

from enum import Enum
from typing import Union

class Status(Enum):
    ACTIVE = 1
    INACTIVE = 0

def process_status(status: Status) -> None:
    print(status.value)

# Type checker sees Status, not int
status: Status = Status.ACTIVE
# value: int = status  # Type error
value: int = status.value  # OK

StrEnum - String-compatible type:

from enum import StrEnum

class Status(StrEnum):
    ACTIVE = "active"
    INACTIVE = "inactive"

def process_status(status: Union[Status, str]) -> None:
    print(status)  # Can use directly

# Type checker sees StrEnum as string-compatible
status: Status = Status.ACTIVE
value: str = status  # OK! No .value needed

4. JSON Serialization

Enum - Custom serialization needed:

from enum import Enum
import json

class Status(Enum):
    ACTIVE = "active"
    INACTIVE = "inactive"

# Default serialization doesn't work
# json.dumps({"status": Status.ACTIVE})  # TypeError

# Need custom encoder
class EnumEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, Enum):
            return obj.value
        return super().default(obj)

data = {"status": Status.ACTIVE}
json_str = json.dumps(data, cls=EnumEncoder)  # {"status": "active"}

StrEnum - Works with JSON directly:

from enum import StrEnum
import json

class Status(StrEnum):
    ACTIVE = "active"
    INACTIVE = "inactive"

# Works directly!
data = {"status": Status.ACTIVE}
json_str = json.dumps(data)  # {"status": "active"} - Works!

5. Dictionary Keys and Values

Enum - Need .value:

from enum import Enum

class Priority(Enum):
    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"

# As dictionary key
priorities = {
    Priority.LOW: 1,
    Priority.MEDIUM: 2,
    Priority.HIGH: 3
}

# As dictionary value - need .value for string
config = {
    "default_priority": Priority.LOW.value  # Need .value
}

StrEnum - Direct usage:

from enum import StrEnum

class Priority(StrEnum):
    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"

# As dictionary key
priorities = {
    Priority.LOW: 1,
    Priority.MEDIUM: 2,
    Priority.HIGH: 3
}

# As dictionary value - direct usage!
config = {
    "default_priority": Priority.LOW  # No .value needed
}

Practical Examples

Example 1: API Endpoints

Using Enum:

from enum import Enum

class Endpoint(Enum):
    USERS = "/api/users"
    POSTS = "/api/posts"
    COMMENTS = "/api/comments"

def make_request(endpoint: Endpoint):
    url = f"https://api.example.com{endpoint.value}"  # Need .value
    # Make request...

Using StrEnum:

from enum import StrEnum

class Endpoint(StrEnum):
    USERS = "/api/users"
    POSTS = "/api/posts"
    COMMENTS = "/api/comments"

def make_request(endpoint: Endpoint):
    url = f"https://api.example.com{endpoint}"  # Direct usage!
    # Make request...

Example 2: Configuration Values

Using Enum:

from enum import Enum

class Environment(Enum):
    DEVELOPMENT = "dev"
    STAGING = "staging"
    PRODUCTION = "prod"

env = Environment.DEVELOPMENT

# Need .value for string operations
database_url = f"postgresql://localhost/{env.value}"
print(f"Environment: {env.value}")

Using StrEnum:

from enum import StrEnum

class Environment(StrEnum):
    DEVELOPMENT = "dev"
    STAGING = "staging"
    PRODUCTION = "prod"

env = Environment.DEVELOPMENT

# Direct usage!
database_url = f"postgresql://localhost/{env}"
print(f"Environment: {env}")

Example 3: File Extensions

Using Enum:

from enum import Enum

class FileType(Enum):
    JSON = ".json"
    XML = ".xml"
    CSV = ".csv"

def get_filename(base: str, file_type: FileType) -> str:
    return base + file_type.value  # Need .value

Using StrEnum:

from enum import StrEnum

class FileType(StrEnum):
    JSON = ".json"
    XML = ".xml"
    CSV = ".csv"

def get_filename(base: str, file_type: FileType) -> str:
    return base + file_type  # Direct usage!

When to Use Each

Use Enum When:

  1. Non-string values needed:

    class Status(Enum):
        PENDING = 1
        PROCESSING = 2
        COMPLETED = 3
  2. Mixed value types:

    class Config(Enum):
        TIMEOUT = 30
        HOST = "localhost"
        PORTS = [8000, 8001]
  3. Numeric enumerations:

    class Priority(Enum):
        LOW = 1
        MEDIUM = 2
        HIGH = 3
  4. Python < 3.11: StrEnum is not available

Use StrEnum When:

  1. String constants only:

    class HTTPMethod(StrEnum):
        GET = "GET"
        POST = "POST"
  2. Direct string usage needed:

    class Color(StrEnum):
        RED = "red"
        BLUE = "blue"
    
    # Want to use directly in strings
    message = f"Color: {Color.RED}"
  3. JSON serialization:

    class Status(StrEnum):
        ACTIVE = "active"
    
    # Works directly with json.dumps
    json.dumps({"status": Status.ACTIVE})
  4. API/Web development: Often need string values

  5. Python 3.11+: Available and recommended for string enums

Advanced Usage

Custom StrEnum with Validation

from enum import StrEnum

class Status(StrEnum):
    ACTIVE = "active"
    INACTIVE = "inactive"
    PENDING = "pending"
    
    @classmethod
    def _missing_(cls, value):
        # Handle case-insensitive lookup
        for member in cls:
            if member.value.lower() == value.lower():
                return member
        return None

# Case-insensitive access
status = Status("ACTIVE")  # Returns Status.ACTIVE

Enum with Auto Values

from enum import Enum, auto

class Status(Enum):
    PENDING = auto()
    PROCESSING = auto()
    COMPLETED = auto()

# Values are 1, 2, 3
print(Status.PENDING.value)  # 1

StrEnum with Auto Values (Python 3.11+)

from enum import StrEnum, auto

class Status(StrEnum):
    PENDING = auto()
    PROCESSING = auto()
    COMPLETED = auto()

# Values are "pending", "processing", "completed"
print(Status.PENDING.value)  # "pending"
print(Status.PENDING)  # "pending" (direct usage)

Functional API

Enum:

from enum import Enum

Status = Enum('Status', ['ACTIVE', 'INACTIVE'], start=1)
# Status.ACTIVE.value = 1

StrEnum:

from enum import StrEnum

Status = StrEnum('Status', ['ACTIVE', 'INACTIVE'])
# Status.ACTIVE.value = "active"
# Status.ACTIVE = "active" (direct usage)

Migration from Enum to StrEnum

If you have existing Enum code with string values:

Before (Enum):

from enum import Enum

class HTTPMethod(Enum):
    GET = "GET"
    POST = "POST"
    PUT = "PUT"

def make_request(method: HTTPMethod, url: str):
    # Need .value everywhere
    requests.request(method.value, url)

After (StrEnum):

from enum import StrEnum

class HTTPMethod(StrEnum):
    GET = "GET"
    POST = "POST"
    PUT = "PUT"

def make_request(method: HTTPMethod, url: str):
    # Direct usage!
    requests.request(method, url)

Common Interview Questions

Q1: What is the main difference between Enum and StrEnum?

  • Enum can have values of any type (int, str, tuple, etc.), while StrEnum only allows string values
  • StrEnum members can be used directly as strings without needing .value, while Enum requires .value for string operations
  • StrEnum is string-compatible for type checking, making it more convenient for string-based operations

Q2: When should you use StrEnum over Enum?

Use StrEnum when:

  • All enum values are strings
  • You need to use enum members directly in string operations
  • You want JSON serialization to work without custom encoders
  • You’re working with APIs, web development, or configuration files
  • You’re using Python 3.11+

Q3: Can you convert an Enum to StrEnum?

Yes, if all values are strings:

from enum import Enum, StrEnum

# Original Enum
class Status(Enum):
    ACTIVE = "active"
    INACTIVE = "inactive"

# Convert to StrEnum
class Status(StrEnum):
    ACTIVE = "active"
    INACTIVE = "inactive"

Q4: What happens if you try to use a non-string value in StrEnum?

You get a TypeError:

from enum import StrEnum

class Status(StrEnum):
    ACTIVE = "active"
    INACTIVE = 1  # TypeError: values must be strings

Q5: How do Enum and StrEnum handle JSON serialization?

  • Enum: Requires custom JSON encoder to serialize .value
  • StrEnum: Works directly with json.dumps() because it’s string-compatible

Summary

Aspect Enum StrEnum
Value Types Any type Strings only
String Usage Requires .value Direct usage
Type Compatibility Generic String-compatible
JSON Serialization Needs custom encoder Works directly
Python Version 3.4+ 3.11+
Use Case General enumerations String constants

Key Takeaway: Use StrEnum when you have string-only enumerations and want the convenience of direct string operations. Use Enum when you need non-string values or are using Python < 3.11.

Interview angle

  • “Why use an Enum over string constants?” - a closed, typed set of values that type checkers verify, with real names in tracebacks and no risk of a typo becoming a silent new value.
  • “What does StrEnum add?” - members are genuine str instances (3.11+), so they serialise to JSON and compare to plain strings without .value. That removes the most common friction with enums at API boundaries.
  • “When would you not use one?” - when the set is genuinely open or defined by external data. An enum that needs updating every time a third party adds a value is a maintenance liability.