Python

P
PythonHub
Progress 0%

Python

  • Home
  • History of Python
  • Applications of Python
  • Introduction To Python
    • What is Python
    • Character Set
    • Tokens in Python
    • Python Execution Mode
    • Variable And Identifiers
    • Data Types in Python
    • Operators And Expressions
    • Constants in Python
    • Assignment Statement
    • Input / Output in Python
    • Simple Python Scripts
    • Namespace in Python
    • šŸ“ Assignments
  • Operators in Python
    • Arithmetic Operators
    • Assignment Operators
    • Shorthand Assignment Operators
    • Relational Operators
    • Logical Operators
    • Bitwise Operators
    • Special Operators
    • šŸ“ Assignments
  • Input Output in Python
    • Accept Input
    • Output Formatting
    • šŸ“ Assignments
  • Conditional Statement
    • Decision Making
    • if Statement
    • IF-ELSE STATEMENT
    • IF-ELSE LADDER
    • NESTED IF-ELSE
    • Short Hand IF-ELSE
    • šŸ“ Assignments
  • Loops
    • Introduction to Loops
    • While Loop
    • Nested While Loop
    • šŸ“ While Loop Assignments
    • For Loop
    • For Loop Examples
    • Nested For Loop
    • Nested For Loop Examples
    • Infinite While Loops
    • Infinite For Loops
    • Break, Continue and Else in Loops
    • Difference Between For and While Loop
    • For Each Loop
    • šŸ“ For Each Assignments
    • šŸ“ All Loops Assignments
  • List
    • List in Python
    • Access List Elements
    • List Functions
    • Iterate (Loop) List
    • List Comprehension
    • šŸ“ Assignments
  • Tuple
    • Tuple in Python
    • Access Tuple Elements
    • Tuple Functions
    • Iterate (Loop) Tuple
    • Unpack Tuple
    • Tuple Comprehension
    • šŸ“ Assignments
  • Set
    • Set in Python
    • Access Set Elements
    • Set Methods
    • Iterate (Loop) Set
    • Pack/Unpack Set
    • Set Comprehension
    • šŸ“ Assignments
  • Dictionary
    • Dictionary
    • Access Dictionary Items
    • Dictionary Methods
    • Iterate (Loop) Dictionary
    • Formatting Dictionaries
    • Nested Dictionaries
    • Dictionary Comprehension
    • šŸ“ Assignments
  • Diff List Tuple Set Dictionary
    • List vs Tuple
    • List vs Set
    • List vs Dictionary
    • Tuple vs Set
    • Tuple vs Dictionary
    • Dictionary vs Set
    • šŸ“ Assignments
  • Exception
    • Error vs Exception
    • Types of Exception
    • Exception Handling
    • User Defined Exception
    • Logging Exception
    • šŸ“ Assignments
  • Functions
    • Introduction to Functions
    • Modular Programming
    • Types of Functions
    • Inbuilt Functions
    • Need For User-Defined Function
    • Elements of User Defined Function
    • Function Arguments
    • Nesting of Functions
    • Recursion
    • Global Local and Non Local
    • Python Lambda Functions
    • šŸ“ Assignments
  • Python Module
    • Introduction to Module
    • Inbuilt Modules in Python
    • User Defined Module
    • šŸ“ Assignments
  • File Handling
    • Introduction to Files
    • Create File
    • Read Files
    • Write to File
    • Rename File
    • Copy File
    • Move File
    • List Files in Directory
    • Binary Files
    • Zipping and Unzipping Files
    • šŸ“ Assignments
  • Strings
    • Basics of Strings
    • String Special Operators
    • String Formatting Operators
    • String Methods
    • šŸ“ Assignments
  • Regular Expressions
  • Python OOPS
    • Basics of Object Oriented
    • What are Classes and Objects?
    • Creating Class and Object
    • OOP vs Procedural Programming
    • Difference Between Classes and Objects
    • Constructors
    • Destructor
    • Built Class Methods and Attributes
    • Class and Instance Variables
    • Inheritance in Python
    • Single Inheritance
    • Multiple Inheritance
    • Multilevel Inheritance
    • Hierarchical Inheritance
    • Hybrid Inheritance
    • Abstraction
    • Method Overriding
    • Abstract Method
    • Interfaces in Python
    • Abstract Class vs Interface
    • Public, Private and Protected
    • Overloading vs Overriding
    • Inheritance vs Composition
    • Encapsulation
    • Polymorphism
    • Inner Classes
    • šŸ“ Assignments
  • Advanced Python
    • Iterator in Python
    • Generator in Python
    • Decorator in Python
    • Type Hints in Python
    • Async/Await Programming
    • Dataclasses in Python
    • Context Managers in Python
    • Property Decorator in Python
    • __slots__ in Python
    • Enums in Python
    • Walrus Operator in Python
    • Match-Case in Python
    • šŸ“ Assignments
  • Python Standard Library
    • Collections Module
    • Itertools Module
    • Functools Module
    • Datetime Module
    • JSON Module
    • OS Module
    • Sys Module
    • Random Module
    • Math Module
    • šŸ“ Assignments
  • Python Testing
    • Unit Testing in Python
    • Pytest Framework
    • Mocking in Python
    • šŸ“ Assignments
  • Python Best Practices
    • PEP 8 Style Guide
    • Docstrings in Python
    • Logging in Python
    • Code Optimization Tips
    • Debugging Techniques
    • šŸ“ Assignments
  • MySQL Database in Python
    • Introduction to MySQL with Python
    • DBMS vs File System
    • Connecting to MySQL Database
    • Create Database in MySQL
    • Create Table in MySQL
    • Insert Data in MySQL
    • Insert Multiple Rows
    • Select Data from MySQL
    • WHERE Clause in MySQL
    • Update Data in MySQL
    • Delete Data from MySQL
    • Parameterized Queries
    • Transaction Management
    • Error Handling
    • Connection Pooling
    • MySQL Drivers Guide
    • Joins in MySQL
    • Aggregation Functions
    • Backup MySQL Database
    • Best Practices
    • šŸ“ Assignments
  • MySQL Database Operations
    • SELECT Statement
    • MySQL Operators
    • DDL Statements
    • DML Statements
    • Subqueries
    • JOIN Operations
    • Aggregation
    • Case Study
    • šŸ“ Assignments
  • Graphics in Python
  • Threads in Python
    • Introduction to Threads
    • Process vs Threads
    • Concurrent Programming & GIL
    • Uses of Threads
    • Creating Threads
    • Single Tasking
    • Multi Tasking
    • Thread Synchronization
    • šŸ“ Assignments
  • Interview Questions & Answers
  • Python Case Studies
  • Multiple Choice Questions
  • šŸ“ Practice Problems
Get in Touch
  • tech2dsm@gmail.com

© Sankalan Data Tech

Python Language Interactive Tutorial

Python: Type Hints

Python Type Hints - Complete Guide

Make your Python code clearer and catch bugs earlier with type hints.

Created by Sankalan Data Tech Team Verified
Data Engineers, Analysts, Scientists & Trainers
Created by experienced Python developers, data engineers, and data scientists to make programming easy through practical examples, real-world experience, and clear explanations.
On this page:
  • What are Type Hints?
  • Why Use Type Hints?
  • Basic Syntax
  • Common Types
  • Type Hints for Collections
  • Advanced Type Hints
  • Real-World Example
  • Best Practices
  • Try It Yourself
  • Quick Quiz
  • Frequently Asked Questions
Share this tutorial:
Twitter LinkedIn Facebook WhatsApp Reddit Telegram Email Copy Link
What You'll Learn Here
  • What are type hints — a way to tell what type of data your code expects
  • Why use them — catch bugs early, better IDE help, clearer code
  • Basic syntax — how to add type hints to variables and functions
  • Common types — int, str, float, bool, None, and more
  • Collections — List, Tuple, Dict, Set
  • Advanced — Optional, Union, Any, TypeVar
  • Real-world use — practical examples you can use

What are Type Hints?

A type hint is a way to tell Python (and other programmers) what type of data a variable should hold or what a function should return.

Think of type hints like labels on boxes. If you have a box labeled "Books", you know what to expect inside. If someone gives you a box labeled "Books" and you open it to find shoes, you know something is wrong.

Type hints don't change how your code runs. They're just information for you, other developers, and your tools (like IDEs and type checkers).

šŸ’” Key concept: Type hints are like notes you leave for yourself and others. They say "this is what I expect" but Python doesn't enforce it at runtime.

Why Use Type Hints?

1

The Benefits of Type Hints

Here's why type hints make your life easier:

# Why Use Type Hints?

print("=" * 50)
print("WHY USE TYPE HINTS?")
print("=" * 50)

# ============================================================
# WITHOUT TYPE HINTS — Confusing and Error-Prone
# ============================================================

print("\nāŒ WITHOUT TYPE HINTS:")

def process_user_data(user):
    """What type is 'user'? What does this return?"""
    # Is user a dict? A class? A list?
    # We have to read the code to know!
    return user.get("name", "Unknown")

# Anyone using this function has to guess what to pass
data = {"name": "Alice", "age": 30}
name = process_user_data(data)
print(f"   Name: {name}")

# But what if someone passes a list?
bad_data = ["Alice", 30]
# This would crash! But we only know at runtime
# name = process_user_data(bad_data)  # AttributeError: 'list' object has no attribute 'get'


# ============================================================
# WITH TYPE HINTS — Clear and Safe
# ============================================================

print("\nāœ… WITH TYPE HINTS:")

from typing import Dict

def process_user_data_typed(user: Dict[str, str]) -> str:
    """Process user data and return the name.
    
    Args:
        user: A dictionary with at least a 'name' key
        
    Returns:
        The user's name as a string
    """
    return user.get("name", "Unknown")

# Now it's clear what to pass!
typed_data: Dict[str, str] = {"name": "Alice", "age": "30"}
name = process_user_data_typed(typed_data)
print(f"   Name: {name}")

# And tools like mypy would catch if you pass the wrong type


# ============================================================
# BENEFITS SUMMARY
# ============================================================

print("\n" + "-" * 30)
print("BENEFITS OF TYPE HINTS")
print("-" * 30)
print("""
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│ BENEFIT                 │ WHAT IT MEANS FOR YOU                     │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│ Better IDE Support      │ Autocomplete shows expected types         │
│                         │                                            │
│ Early Bug Detection     │ Tools like mypy catch type errors         │
│                         │ before running code                       │
│                         │                                            │
│ Clearer Code            │ Anyone reading your code knows what       │
│                         │ to expect                                 │
│                         │                                            │
│ Better Documentation    │ Type hints act as built-in documentation  │
│                         │                                            │
│ Easier Refactoring      │ Changes are easier to make safely         │
│                         │                                            │
│ More Maintainable       │ Teams can work together more easily       │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
""")

Benefits of type hints:

  • Better IDE support — autocomplete and error highlighting
  • Early bug detection — catch errors before running
  • Clearer code — everyone knows what's expected
  • Better documentation — type hints explain the code
  • Easier refactoring — changes are safer

Quick Check: Do type hints change how your code runs? (Answer: No — they're just information for developers and tools)

Basic Syntax

2

How to Write Type Hints

Adding type hints is easy. You just add : type after variable names and -> type after function definitions.

# Basic Type Hint Syntax

print("=" * 50)
print("BASIC SYNTAX")
print("=" * 50)

# ============================================================
# VARIABLES
# ============================================================

print("\n1. VARIABLE TYPE HINTS")

# Without type hint (normal Python)
name = "Alice"
age = 30
price = 19.99
is_active = True

# With type hint (adds clarity)
name: str = "Alice"
age: int = 30
price: float = 19.99
is_active: bool = True

# You can also declare without assigning
user_id: int
user_id = 123

print(f"   name: {name} (type hint: str)")
print(f"   age: {age} (type hint: int)")
print(f"   price: {price} (type hint: float)")
print(f"   is_active: {is_active} (type hint: bool)")


# ============================================================
# FUNCTIONS
# ============================================================

print("\n2. FUNCTION TYPE HINTS")

# Without type hints
def add(a, b):
    return a + b

# With type hints (clear what's expected)
def add_typed(a: int, b: int) -> int:
    """Add two numbers and return the result"""
    return a + b

print(f"   add_typed(5, 3) -> {add_typed(5, 3)}")


# ============================================================
# RETURNING DIFFERENT TYPES
# ============================================================

print("\n3. RETURNING DIFFERENT TYPES")

def greet(name: str, formal: bool = False) -> str:
    """Return a greeting based on formality"""
    if formal:
        return f"Good day, {name}!"
    return f"Hello, {name}!"

print(f"   greet('Alice') -> {greet('Alice')}")
print(f"   greet('Bob', True) -> {greet('Bob', True)}")


# ============================================================
# NO RETURN VALUE (None)
# ============================================================

print("\n4. FUNCTIONS THAT RETURN NOTHING")

def log_message(message: str) -> None:
    """Log a message (returns nothing)"""
    print(f"   LOG: {message}")

log_message("System started")
# Returns None


# ============================================================
# COMPLEX TYPES
# ============================================================

print("\n5. MORE COMPLEX EXAMPLES")

from typing import List, Dict, Tuple

# Function that takes a list and returns a list
def double_numbers(numbers: List[int]) -> List[int]:
    return [n * 2 for n in numbers]

# Function that takes a dict and returns a tuple
def get_user_info(user: Dict[str, str]) -> Tuple[str, str]:
    return (user.get("name", "Unknown"), user.get("email", "unknown@example.com"))

numbers = [1, 2, 3, 4, 5]
doubled = double_numbers(numbers)
print(f"   double_numbers({numbers}) -> {doubled}")

user = {"name": "Alice", "email": "alice@example.com"}
name, email = get_user_info(user)
print(f"   get_user_info({user}) -> ({name}, {email})")

Basic syntax key points:

  • Variable hint — name: str = "Alice"
  • Parameter hint — def greet(name: str):
  • Return hint — def greet() -> str:
  • No return — -> None for functions that return nothing
  • Type hints are optional — you can add them gradually

Quick Check: How do you add a type hint to a function parameter? (Answer: def function(param: type):)

Common Types

3

Basic Type Hints You'll Use Every Day

Here are the most common types you'll use with type hints:

# Common Types in Type Hints

print("=" * 50)
print("COMMON TYPES")
print("=" * 50)

# ============================================================
# BASIC TYPES
# ============================================================

print("\n1. BASIC TYPES")

# int — whole numbers
age: int = 25
print(f"   age: {age} (int)")

# str — text
name: str = "Alice"
print(f"   name: {name} (str)")

# float — decimal numbers
price: float = 19.99
print(f"   price: {price} (float)")

# bool — True or False
is_active: bool = True
print(f"   is_active: {is_active} (bool)")

# None — no value (use NoneType or Optional)
result: None = None
print(f"   result: {result} (None)")


# ============================================================
# COLLECTION TYPES (need typing module)
# ============================================================

print("\n2. COLLECTION TYPES")

from typing import List, Tuple, Dict, Set

# List — list of items
numbers: List[int] = [1, 2, 3, 4, 5]
print(f"   numbers: {numbers} (List[int])")

# Tuple — fixed-length collection
person: Tuple[str, int] = ("Alice", 30)
print(f"   person: {person} (Tuple[str, int])")

# Dict — key-value pairs
user: Dict[str, str] = {"name": "Alice", "email": "alice@example.com"}
print(f"   user: {user} (Dict[str, str])")

# Set — unique items
tags: Set[str] = {"python", "programming", "tutorial"}
print(f"   tags: {tags} (Set[str])")


# ============================================================
# NESTED TYPES
# ============================================================

print("\n3. NESTED TYPES")

# List of lists (matrix)
matrix: List[List[int]] = [[1, 2], [3, 4], [5, 6]]
print(f"   matrix: {matrix} (List[List[int]])")

# Dictionary with list values
data: Dict[str, List[int]] = {"scores": [95, 87, 92]}
print(f"   data: {data} (Dict[str, List[int]])")

# Tuple with different types
student: Tuple[str, int, List[str]] = ("Bob", 20, ["Math", "Science"])
print(f"   student: {student} (Tuple[str, int, List[str]])")


# ============================================================
# TYPE ALIASES (make code cleaner)
# ============================================================

print("\n4. TYPE ALIASES")

# Give a complex type a simple name
Person = Tuple[str, int, str]  # name, age, email
people: List[Person] = [
    ("Alice", 30, "alice@example.com"),
    ("Bob", 25, "bob@example.com")
]

print(f"   people: {people} (List[Person])")

# Another alias
Employee = Dict[str, str | int]  # Python 3.10+ union syntax
employees: List[Employee] = [
    {"name": "Alice", "age": 30},
    {"name": "Bob", "age": 25}
]
print(f"   employees: {employees} (List[Employee])")


# ============================================================
# QUICK REFERENCE
# ============================================================

print("\n" + "-" * 30)
print("QUICK REFERENCE")
print("-" * 30)
print("""
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│ TYPE                │ HINT                                               │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│ int                 │ age: int = 25                                      │
│ str                 │ name: str = "Alice"                               │
│ float               │ price: float = 19.99                              │
│ bool                │ active: bool = True                               │
│ List[int]           │ numbers: List[int] = [1, 2, 3]                    │
│ Tuple[str, int]     │ person: Tuple[str, int] = ("Alice", 30)           │
│ Dict[str, str]      │ user: Dict[str, str] = {"key": "value"}           │
│ Set[str]            │ tags: Set[str] = {"a", "b"}                       │
│ List[List[int]]     │ matrix: List[List[int]] = [[1, 2], [3, 4]]        │
│ None                │ result: None = None                               │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
""")

Common types key points:

  • Basic types — int, str, float, bool, None
  • Collections — List, Tuple, Dict, Set (from typing module)
  • Nested types — List[List[int]], Dict[str, List[int]]
  • Type aliases — give complex types simple names

Quick Check: What module do you need to import for List, Dict, and Tuple? (Answer: from typing import List, Dict, Tuple)

Type Hints for Collections

4

Working with Lists, Tuples, Dictionaries, and Sets

Collections are everywhere in Python. Here's how to add type hints to them.

# Type Hints for Collections

print("=" * 50)
print("COLLECTION TYPE HINTS")
print("=" * 50)

from typing import List, Tuple, Dict, Set, Any

# ============================================================
# LISTS
# ============================================================

print("\n1. LISTS")

# List of strings
names: List[str] = ["Alice", "Bob", "Charlie"]
print(f"   names: {names}")

# List of integers
scores: List[int] = [95, 87, 92, 78]
print(f"   scores: {scores}")

# List of mixed types (use Any or Union)
mixed: List[Any] = ["Alice", 30, 19.99, True]
print(f"   mixed: {mixed}")

# Function that works with lists
def get_average(numbers: List[float]) -> float:
    return sum(numbers) / len(numbers)

avg = get_average([10.5, 20.5, 30.5])
print(f"   Average: {avg:.2f}")


# ============================================================
# TUPLES
# ============================================================

print("\n2. TUPLES")

# Fixed-size tuple
person: Tuple[str, int] = ("Alice", 30)
print(f"   person: {person}")

# Tuple with more items
employee: Tuple[str, int, str] = ("Bob", 25, "Engineer")
print(f"   employee: {employee}")

# Empty tuple
empty: Tuple[()] = ()
print(f"   empty: {empty}")

# Variable-length tuple (any number of strings)
tags: Tuple[str, ...] = ("python", "programming", "tutorial", "tips")
print(f"   tags: {tags}")


# ============================================================
# DICTIONARIES
# ============================================================

print("\n3. DICTIONARIES")

# String keys, string values
user: Dict[str, str] = {"name": "Alice", "email": "alice@example.com"}
print(f"   user: {user}")

# String keys, int values
scores_dict: Dict[str, int] = {"Alice": 95, "Bob": 87, "Charlie": 92}
print(f"   scores_dict: {scores_dict}")

# String keys, mixed values
profile: Dict[str, Any] = {"name": "Alice", "age": 30, "active": True}
print(f"   profile: {profile}")

# Function that works with dictionaries
def get_user_name(user: Dict[str, str]) -> str:
    return user.get("name", "Unknown")

print(f"   get_user_name: {get_user_name(user)}")


# ============================================================
# SETS
# ============================================================

print("\n4. SETS")

# Set of strings
tags_set: Set[str] = {"python", "programming", "tutorial"}
print(f"   tags_set: {tags_set}")

# Set of integers
numbers_set: Set[int] = {1, 2, 3, 4, 5}
print(f"   numbers_set: {numbers_set}")

# Function that works with sets
def has_tag(tags: Set[str], tag: str) -> bool:
    return tag in tags

print(f"   has_tag: {has_tag(tags_set, 'python')}")


# ============================================================
# PRACTICAL EXAMPLES
# ============================================================

print("\n5. PRACTICAL EXAMPLES")

# Process a list of dictionaries
def process_users(users: List[Dict[str, Any]]) -> List[str]:
    """Extract names from a list of user dictionaries"""
    names = []
    for user in users:
        if "name" in user:
            names.append(user["name"])
    return names

users_list: List[Dict[str, Any]] = [
    {"name": "Alice", "age": 30},
    {"name": "Bob", "age": 25},
    {"name": "Charlie", "age": 35}
]

result = process_users(users_list)
print(f"   Users: {users_list}")
print(f"   Names: {result}")

# Group scores by subject
def group_scores(scores: List[Tuple[str, int]]) -> Dict[str, List[int]]:
    """Group scores by subject"""
    grouped: Dict[str, List[int]] = {}
    for subject, score in scores:
        if subject not in grouped:
            grouped[subject] = []
        grouped[subject].append(score)
    return grouped

scores_list: List[Tuple[str, int]] = [
    ("Math", 95), ("Science", 88), ("Math", 92), ("Science", 90)
]
grouped = group_scores(scores_list)
print(f"   Grouped scores: {grouped}")

Collection type hints key points:

  • List — List[str] for a list of strings
  • Tuple — Tuple[str, int] for a fixed-size tuple
  • Dict — Dict[str, int] for string keys and int values
  • Set — Set[str] for a set of strings
  • Nested — List[Dict[str, Any]] for complex structures

Quick Check: How do you type hint a dictionary with string keys and integer values? (Answer: Dict[str, int])

Advanced Type Hints

5

Optional, Union, Any, and More

Sometimes a variable can be one of several types, or it might be missing. Here's how to handle those situations.

# Advanced Type Hints

print("=" * 50)
print("ADVANCED TYPE HINTS")
print("=" * 50)

from typing import Optional, Union, Any, TypeVar, Callable
from typing import List, Dict

# ============================================================
# Optional — Could be None
# ============================================================

print("\n1. OPTIONAL — Maybe None")

# Before: using Union
def get_user_old(user_id: int) -> Union[str, None]:
    """Return user name or None if not found"""
    if user_id == 1:
        return "Alice"
    return None

# Better: using Optional
def get_user(user_id: int) -> Optional[str]:
    """Return user name or None if not found"""
    if user_id == 1:
        return "Alice"
    return None

# Optional is just a shorthand for Union[T, None]
print(f"   get_user(1): {get_user(1)}")
print(f"   get_user(2): {get_user(2)}")

# Optional with default values
def greet_user(name: Optional[str] = None) -> str:
    if name:
        return f"Hello, {name}!"
    return "Hello, stranger!"

print(f"   greet_user('Alice'): {greet_user('Alice')}")
print(f"   greet_user(): {greet_user()}")


# ============================================================
# Union — Multiple Possible Types
# ============================================================

print("\n2. UNION — Multiple Types")

# Before Python 3.10 (using Union from typing)
def process_value_old(value: Union[int, str]) -> str:
    return f"Value: {value}"

# Python 3.10+ (using | syntax)
def process_value(value: int | str) -> str:
    return f"Value: {value}"

print(f"   process_value(5): {process_value(5)}")
print(f"   process_value('hello'): {process_value('hello')}")

# More complex union
def handle_data(data: int | str | List[int]) -> str:
    if isinstance(data, int):
        return f"Integer: {data}"
    elif isinstance(data, str):
        return f"String: {data}"
    elif isinstance(data, list):
        return f"List: {data}"
    return "Unknown"

print(f"   handle_data(10): {handle_data(10)}")
print(f"   handle_data('test'): {handle_data('test')}")
print(f"   handle_data([1, 2, 3]): {handle_data([1, 2, 3])}")


# ============================================================
# Any — Any Type (use sparingly)
# ============================================================

print("\n3. ANY — Any Type")

from typing import Any

def log_data(data: Any) -> None:
    """Log any type of data"""
    print(f"   Log: {data}")

# Any can be anything
log_data("Hello")
log_data(123)
log_data([1, 2, 3])
log_data({"name": "Alice"})

print("   āš ļø Use Any sparingly — it defeats the purpose of type hints!")


# ============================================================
# TypeVar — Generic Types
# ============================================================

print("\n4. TYPEVAR — Generic Types")

from typing import TypeVar

T = TypeVar('T')  # A generic type variable

def first_item(items: List[T]) -> T:
    """Return the first item from any list"""
    return items[0] if items else None

# Works with any type
print(f"   first_item([1, 2, 3]): {first_item([1, 2, 3])}")
print(f"   first_item(['a', 'b', 'c']): {first_item(['a', 'b', 'c'])}")


# ============================================================
# Callable — Function Types
# ============================================================

print("\n5. CALLABLE — Function Types")

from typing import Callable

def apply_operation(x: int, y: int, operation: Callable[[int, int], int]) -> int:
    """Apply an operation to two numbers"""
    return operation(x, y)

def add(a: int, b: int) -> int:
    return a + b

def multiply(a: int, b: int) -> int:
    return a * b

print(f"   apply_operation(5, 3, add): {apply_operation(5, 3, add)}")
print(f"   apply_operation(5, 3, multiply): {apply_operation(5, 3, multiply)}")


# ============================================================
# SUMMARY
# ============================================================

print("\n" + "-" * 30)
print("SUMMARY — ADVANCED TYPES")
print("-" * 30)
print("""
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│ TYPE                │ WHAT IT MEANS                                      │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│ Optional[T]         │ Could be T or None                                │
│ Union[T, U]         │ Could be T or U                                   │
│ T | U               │ Same as Union (Python 3.10+)                      │
│ Any                 │ Any type (use sparingly)                          │
│ TypeVar('T')        │ Generic type (works with any type)                │
│ Callable            │ A function type                                   │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
""")

Advanced type hints key points:

  • Optional — Optional[str] means string or None
  • Union — Union[int, str] means int or string
  • Any — Any means any type (use sparingly)
  • TypeVar — T = TypeVar('T') for generic types
  • Callable — Callable[[int, int], int] for functions

Quick Check: When should you use Optional? (Answer: When a variable can be None in addition to its main type)

Real-World Example

6

Building a Data Processing Pipeline

# Real-World Example: Data Processing Pipeline

from typing import List, Dict, Optional, Tuple, Any
import json
from datetime import datetime

print("=" * 60)
print("DATA PROCESSING PIPELINE")
print("=" * 60)

# ============================================================
# DATA MODELS (with type hints)
# ============================================================

class DataProcessor:
    """Process data with clear type hints"""
    
    def __init__(self, config: Dict[str, Any]) -> None:
        """Initialize the processor with configuration"""
        self.config = config
        self.processed_count: int = 0
        self.errors: List[str] = []
    
    def load_data(self, file_path: str) -> List[Dict[str, Any]]:
        """Load data from a JSON file"""
        try:
            with open(file_path, 'r') as f:
                data = json.load(f)
            return data if isinstance(data, list) else []
        except (FileNotFoundError, json.JSONDecodeError) as e:
            self.errors.append(f"Error loading data: {e}")
            return []
    
    def filter_data(self, data: List[Dict[str, Any]], field: str, value: Any) -> List[Dict[str, Any]]:
        """Filter data by a specific field value"""
        return [item for item in data if item.get(field) == value]
    
    def transform_data(self, data: List[Dict[str, Any]]) -> List[Dict[str, str]]:
        """Transform data to a standard format"""
        result: List[Dict[str, str]] = []
        for item in data:
            transformed = {
                "id": str(item.get("id", "")),
                "name": item.get("name", "Unknown"),
                "email": item.get("email", "unknown@example.com"),
                "processed_at": datetime.now().isoformat()
            }
            result.append(transformed)
        return result
    
    def save_data(self, data: List[Dict[str, str]], output_path: str) -> bool:
        """Save processed data to a file"""
        try:
            with open(output_path, 'w') as f:
                json.dump(data, f, indent=2)
            return True
        except Exception as e:
            self.errors.append(f"Error saving data: {e}")
            return False
    
    def process(self, input_file: str, output_file: str) -> Tuple[bool, int]:
        """Run the complete processing pipeline"""
        print(f"   šŸ“ Processing: {input_file}")
        
        # Step 1: Load data
        raw_data = self.load_data(input_file)
        if not raw_data:
            return (False, 0)
        
        # Step 2: Filter data (optional)
        filter_field = self.config.get("filter_field")
        filter_value = self.config.get("filter_value")
        
        if filter_field and filter_value is not None:
            raw_data = self.filter_data(raw_data, filter_field, filter_value)
        
        # Step 3: Transform data
        processed_data = self.transform_data(raw_data)
        
        # Step 4: Save data
        success = self.save_data(processed_data, output_file)
        
        self.processed_count = len(processed_data)
        return (success, self.processed_count)
    
    def get_stats(self) -> Dict[str, Any]:
        """Get processing statistics"""
        return {
            "processed_count": self.processed_count,
            "error_count": len(self.errors),
            "last_error": self.errors[-1] if self.errors else None
        }


# ============================================================
# SIMULATED DATA
# ============================================================

def create_sample_data() -> List[Dict[str, Any]]:
    """Create sample data for testing"""
    return [
        {"id": 1, "name": "Alice", "email": "alice@example.com", "status": "active"},
        {"id": 2, "name": "Bob", "email": "bob@example.com", "status": "active"},
        {"id": 3, "name": "Charlie", "email": "charlie@example.com", "status": "inactive"},
        {"id": 4, "name": "Diana", "email": "diana@example.com", "status": "active"}
    ]


# ============================================================
# DEMONSTRATION
# ============================================================

print("\n1. CREATING PROCESSOR")
config: Dict[str, Any] = {
    "filter_field": "status",
    "filter_value": "active"
}

processor = DataProcessor(config)
print(f"   Config: {config}")

print("\n2. SIMULATING DATA PROCESSING")
# In real use, you'd read from a file
# For demo, we'll use sample data
sample_data = create_sample_data()
print(f"   Sample data: {sample_data}")

print("\n3. FILTERING DATA")
active_users = processor.filter_data(sample_data, "status", "active")
print(f"   Active users: {active_users}")

print("\n4. TRANSFORMING DATA")
transformed = processor.transform_data(active_users)
print(f"   Transformed data: {transformed}")

print("\n5. GETTING STATS")
stats = processor.get_stats()
print(f"   Stats: {stats}")

print("\n" + "=" * 60)
print("KEY TAKEAWAYS:")
print("=" * 60)
print("āœ… Type hints make the code self-documenting")
print("āœ… Functions clearly show what they expect and return")
print("āœ… Complex types are easy to understand")
print("āœ… IDEs provide better autocomplete and error checking")
print("āœ… The code is easier to maintain and debug")

Real-world example key points:

  • Clear function signatures — know what each function expects
  • Complex types — List[Dict[str, Any]] makes it clear
  • Return types — Tuple[bool, int] shows what's returned
  • Self-documenting — type hints explain the code
  • Better tools — IDEs and type checkers work better

Quick Check: What does List[Dict[str, Any]] mean? (Answer: A list of dictionaries where keys are strings and values can be any type)

Best Practices

7

Using Type Hints Effectively

# Best Practices for Type Hints

print("=" * 60)
print("BEST PRACTICES FOR TYPE HINTS")
print("=" * 60)

from typing import List, Dict, Optional, Any

# ============================================================
# 1. BE SPECIFIC (AVOID ANY)
# ============================================================

print("\n1. BE SPECIFIC — AVOID ANY")

# āŒ BAD: Using Any when you could be specific
def process_data_bad(data: Any) -> Any:
    return data

# āœ… GOOD: Be specific about what you expect
def process_data_good(data: List[Dict[str, str]]) -> Dict[str, List[str]]:
    result: Dict[str, List[str]] = {}
    for item in data:
        for key, value in item.items():
            result.setdefault(key, []).append(value)
    return result

print("   āœ… Specific types make code clearer and safer")


# ============================================================
# 2. USE OPTIONAL FOR VALUES THAT CAN BE NONE
# ============================================================

print("\n2. USE OPTIONAL FOR NONE VALUES")

# āŒ BAD: Using Union
def find_user_bad(user_id: int) -> Union[Dict, None]:
    return None

# āœ… GOOD: Using Optional (clearer intention)
def find_user_good(user_id: int) -> Optional[Dict[str, str]]:
    # Returns dict or None
    return None

print("   āœ… Optional[T] = Union[T, None] but clearer")


# ============================================================
# 3. USE TYPE ALIASES FOR COMPLEX TYPES
# ============================================================

print("\n3. USE TYPE ALIASES FOR COMPLEX TYPES")

# āŒ BAD: Repeating complex types
def process_user_data(data: List[Dict[str, Union[str, int, float]]]) -> None:
    pass

def analyze_user_data(data: List[Dict[str, Union[str, int, float]]]) -> Dict:
    pass

# āœ… GOOD: Create a type alias
from typing import TypeAlias

UserData: TypeAlias = List[Dict[str, Union[str, int, float]]]

def process_user_data_alias(data: UserData) -> None:
    pass

def analyze_user_data_alias(data: UserData) -> Dict:
    pass

print("   āœ… Type aliases make code cleaner and easier to change")


# ============================================================
# 4. USE TYPE HINTS IN CLASSES
# ============================================================

print("\n4. USE TYPE HINTS IN CLASSES")

class User:
    """User class with type hints"""
    
    def __init__(self, name: str, age: int, email: Optional[str] = None) -> None:
        self.name: str = name
        self.age: int = age
        self.email: Optional[str] = email
        self.is_active: bool = True
        self.friends: List[str] = []
    
    def get_info(self) -> Dict[str, Any]:
        return {
            "name": self.name,
            "age": self.age,
            "email": self.email,
            "is_active": self.is_active,
            "friends": self.friends
        }
    
    def add_friend(self, friend_name: str) -> None:
        self.friends.append(friend_name)

user = User("Alice", 30)
user.add_friend("Bob")
print(f"   User: {user.get_info()}")


# ============================================================
# 5. USE MYPY FOR TYPE CHECKING
# ============================================================

print("\n5. USE MYPY FOR TYPE CHECKING")

print("   āœ… Install mypy: pip install mypy")
print("   āœ… Run mypy: mypy your_file.py")
print("   āœ… mypy will catch type errors before you run")


# ============================================================
# 6. START SMALL
# ============================================================

print("\n6. START SMALL")

print("""
   āœ… Don't try to add type hints to everything at once
   āœ… Start with new code or code you're changing
   āœ… Gradually add hints to existing code
   āœ… Focus on: function parameters, return values, and public APIs
""")


# ============================================================
# SUMMARY
# ============================================================

print("\n" + "=" * 60)
print("BEST PRACTICES SUMMARY")
print("=" * 60)
print("""
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│ PRACTICE                    │ WHY IT MATTERS                             │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│ Be specific, avoid Any      │ Makes code clearer and safer               │
│                             │                                             │
│ Use Optional for None       │ Clearly shows when None is possible        │
│                             │                                             │
│ Use type aliases            │ Simplifies complex types                   │
│                             │                                             │
│ Use hints in classes        │ Makes OOP code more readable              │
│                             │                                             │
│ Use mypy for checking       │ Catches errors early                       │
│                             │                                             │
│ Start small                 │ Don't overwhelm yourself                   │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

šŸ“Œ REMEMBER:
   • Type hints are optional but helpful
   • They don't change how your code runs
   • They make your code better for others and for yourself
   • Use tools like mypy to check your types
""")

Best practices summary:

  • Be specific — avoid Any when possible
  • Use Optional — when values can be None
  • Use type aliases — simplify complex types
  • Use hints in classes — makes OOP code clearer
  • Use mypy — catch type errors early
  • Start small — add hints gradually

Quick Check: What tool can you use to check type hints? (Answer: mypy)

Try It Yourself

Experiment with type hints in the editor below.

Loading Pyodide... 0%
Python Code Editor
==================================================
TYPE HINTS - PRACTICE
==================================================

1. BASIC TYPE HINTS
add_numbers(5, 3): 8
greet_user('Alice'): Hello, Alice!
is_adult(20): True
is_adult(16): False

2. COLLECTION TYPE HINTS
get_averages([85, 90, 78, 92, 88]): 86.60
get_user_names([{'name': 'Alice', 'email': 'alice@example.com'}, {'name': 'Bob', 'email': 'bob@example.com'}]): ['Alice', 'Bob']

3. OPTIONAL AND UNION
find_user(1): Alice
find_user(99): None
process_value(10): Processed: 10
process_value('hello'): Processed: hello

4. COMPLEX TYPES
Student: ('Alice', 20, ['Math', 'Science', 'English'])
Info: {'name': 'Alice', 'age': 20, 'subject_count': 3, 'subjects': ['Math', 'Science', 'English']}
šŸ†

You've Got It!

You now understand type hints in Python. You know how to add them to variables, functions, and classes, and you understand advanced types like Optional and Union.

Quick Quiz

Test what you've learned:

1. What do type hints do in Python?
2. How do you add a type hint for a parameter?
3. What does Optional[str] mean?
4. What module do you import for List, Dict, and Tuple?
5. What tool checks type hints?

Frequently Asked Questions

What are type hints in Python? ā–¼

Type hints are a way to specify the expected type of variables, function parameters, and return values. They help make code clearer and can be used by tools like mypy to catch errors early.

Do type hints affect how my code runs? ā–¼

No! Type hints are just annotations. They don't change how your code runs at all. Python ignores them at runtime. They're for developers and tools only.

Do I need to use type hints? ā–¼

No, type hints are completely optional. You can write Python code without them. But they're very helpful for making your code clearer, getting better IDE support, and catching bugs early.

What's the difference between Optional and Union? ā–¼

Optional[T] is a shorthand for Union[T, None]. It means the value can be type T or None. Union[T, U] means the value can be type T or type U.

Can I use type hints with classes? ā–¼

Yes! You can use type hints in class methods, instance variables, and even for the class itself. This makes object-oriented code much clearer and easier to work with.

What's the difference between Python 3.10 union syntax and typing.Union? ā–¼

Python 3.10 introduced the | syntax for unions (e.g., int | str). This is simpler and cleaner than Union[int, str]. Both work, but the | syntax is newer and preferred in Python 3.10+.

Where to Go From Here

Now that you understand type hints in Python, check out these related topics:

Dataclasses

Learn how dataclasses work with type hints.

Learn More →

Decorators

Learn about decorators — another way to enhance functions.

Learn More →

Async/Await

Learn about asynchronous programming with type hints.

Learn More →
Interview Resources
  • Python Syntax & Variables Interview Questions
  • Top SQL Interview Questions & Answers
  • SQL Joins: Displaying Data from Multiple Tables FAQ
  • Python Lists and Dictionaries Interview Questions
  • Python OOP Interview Questions
  • SQL Set Operators Interview Questions
Previous: Decorators Next: Async/Await →