# Difference between Mutable and Immutable Data types in Python

### **Mutable Data Types**

* **Definition**: Objects of mutable data types can be changed after their creation. You can modify the object without creating a new one.
    
* **Examples**: `list`, `dict`, `set`, `bytearray`
    
* **Behavior**:
    
    * You can change, add, or remove elements in place.
        
    * These changes reflect directly in all references to the object.
        

#### Example:

#### **1\. List**

* **Addition**:
    
    * `append(element)`: Adds an element to the end of the list.
        
    * `insert(index, element)`: Inserts an element at a specified index.
        
    * `extend(iterable)`: Adds all elements of an iterable (like another list) to the end of the list.
        
    
    ```python
    my_list = [1, 2, 3]
    my_list.append(4)  # [1, 2, 3, 4]
    my_list.insert(1, 5)  # [1, 5, 2, 3, 4]
    my_list.extend([6, 7])  # [1, 5, 2, 3, 4, 6, 7]
    ```
    
* **Removal**:
    
    * `pop(index)`: Removes and returns the element at the specified index (default is the last element).
        
    * `remove(element)`: Removes the first occurrence of the specified element.
        
    * `clear()`: Removes all elements from the list.
        
    
    ```python
    my_list = [1, 2, 3, 4]
    my_list.pop()  # Removes 4 -> [1, 2, 3]
    my_list.remove(2)  # Removes 2 -> [1, 3]
    my_list.clear()  # [] (empty list)
    ```
    
* **Traversal**: Use a `for` loop to iterate over the list.
    
    ```python
    my_list = [1, 2, 3]
    for item in my_list:
        print(item)
    ```
    

---

### **2\. Dictionary**

* **Addition**:
    
    * Add a key-value pair directly: `dict[key] = value`.
        
    * `update(dict_or_iterable)`: Updates the dictionary with key-value pairs from another dictionary or iterable.
        
    
    ```python
    my_dict = {"a": 1}
    my_dict["b"] = 2  # {"a": 1, "b": 2}
    my_dict.update({"c": 3})  # {"a": 1, "b": 2, "c": 3}
    ```
    
* **Removal**:
    
    * `pop(key)`: Removes and returns the value associated with the key.
        
    * `popitem()`: Removes and returns the last inserted key-value pair.
        
    * `del dict[key]`: Deletes a key-value pair.
        
    * `clear()`: Removes all key-value pairs.
        
    
    ```python
    my_dict = {"a": 1, "b": 2}
    my_dict.pop("a")  # {"b": 2}
    my_dict.popitem()  # Removes the last pair -> {}
    del my_dict["b"]  # KeyError if key doesn't exist
    ```
    
* **Traversal**: Use a `for` loop to iterate over:
    
    * Keys: `for key in my_dict:`
        
    * Values: `for value in my_dict.values():`
        
    * Key-Value pairs: `for key, value in my_dict.items():`
        
    
    ```python
    my_dict = {"a": 1, "b": 2}
    for key, value in my_dict.items():
        print(f"{key}: {value}")
    ```
    

---

### **3\. Set**

* **Addition**:
    
    * `add(element)`: Adds a single element to the set.
        
    * `update(iterable)`: Adds multiple elements from an iterable.
        
    
    ```python
    my_set = {1, 2, 3}
    my_set.add(4)  # {1, 2, 3, 4}
    my_set.update([5, 6])  # {1, 2, 3, 4, 5, 6}
    ```
    
* **Removal**:
    
    * `remove(element)`: Removes the specified element (raises `KeyError` if not found).
        
    * `discard(element)`: Removes the specified element (does nothing if not found).
        
    * `pop()`: Removes and returns an arbitrary element.
        
    * `clear()`: Removes all elements.
        
    
    ```python
    my_set = {1, 2, 3}
    my_set.remove(2)  # {1, 3}
    my_set.discard(4)  # {1, 3} (no error even if 4 is not present)
    my_set.pop()  # Removes an arbitrary element
    ```
    
* **Traversal**: Use a `for` loop to iterate over the set (note: order is not guaranteed).
    
    ```python
    my_set = {1, 2, 3}
    for item in my_set:
        print(item)
    ```
    

---

### Summary Table

| **Data Type** | **Addition** | **Removal** | **Traversal** |
| --- | --- | --- | --- |
| **List** | `append`, `insert`, `extend` | `pop`, `remove`, `clear` | `for item in list` |
| **Dictionary** | `dict[key] = value`, `update` | `pop`, `popitem`, `clear`, `del` | `for key, value in dict.items()` |
| **Set** | `add`, `update` | `remove`, `discard`, `pop`, `clear` | `for item in set` |

### **Immutable Data Types**

* **Definition**: Objects of immutable data types cannot be changed after their creation. If you attempt to modify the object, a new object is created.
    
* **Examples**: `int`, `float`, `str`, `tuple`, `frozenset`, `bytes`
    
* **Behavior**:
    
    * Any "modification" creates a new object, leaving the original object unchanged.
        
    * Immutable types are hashable and can be used as keys in dictionaries.
        

#### Example:

**1\. Tuple**

* **Addition**:
    
    * Tuples are immutable, so you cannot add elements to an existing tuple.
        
    * Instead, you can create a new tuple by concatenation.
        
    
    ```python
    t = (1, 2, 3)
    new_tuple = t + (4,)  # Creates a new tuple (1, 2, 3, 4)
    ```
    
* **Removal**:
    
    * Not allowed. You must create a new tuple excluding the element.
        
    
    ```python
    t = (1, 2, 3)
    new_tuple = tuple(x for x in t if x != 2)  # Creates a new tuple (1, 3)
    ```
    
* **Traversal**:
    
    * Use a `for` loop to iterate over elements.
        
    
    ```python
    t = (1, 2, 3)
    for item in t:
        print(item)
    ```
    
* **Hashable**:
    
    * **Hashable means the object has a unique, fixed hash value that remains constant during its lifetime.**
        
    * Since tuples are immutable, they are hashable if all their elements are also hashable.
        
    * Example:
        
        ```python
        t = (1, "hello")
        print(hash(t))  # Hash value (works since all elements are hashable)
        ```
        

---

### **2\. String**

* **Addition**:
    
    * Strings are immutable, so addition creates a new string (concatenation).
        
    
    ```python
    s = "hello"
    new_string = s + " world"  # Creates a new string "hello world"
    ```
    
* **Removal**:
    
    * Not allowed directly. Create a new string excluding unwanted parts.
        
    
    ```python
    s = "hello"
    new_string = s.replace("e", "")  # Removes "e", creating "hllo"
    ```
    
* **Traversal**:
    
    * Use a `for` loop to iterate over characters.
        
    
    ```python
    s = "hello"
    for char in s:
        print(char)
    ```
    
* **Hashable**:
    
    * Strings are hashable because they are immutable.
        
    * Example:
        
        ```python
        s = "hello"
        print(hash(s))  # Outputs the hash value of the string
        ```
        

---

### **3\. Integer**

* **Addition**:
    
    * Integers are immutable. Any addition operation creates a new integer.
        
    
    ```python
    x = 5
    y = x + 1  # Creates a new integer 6
    ```
    
* **Removal**:
    
    * Not applicable for integers.
        
* **Traversal**:
    
    * Not applicable because integers are single values, not collections.
        
* **Hashable**:
    
    * Integers are hashable as they are immutable.
        
    * Example:
        
        ```python
        x = 42
        print(hash(x))  # Outputs the hash value of the integer
        ```
        

---

### **4\. Float**

* **Addition**:
    
    * Floats are immutable. Any arithmetic creates a new float object.
        
    
    ```python
    x = 3.14
    y = x + 2.0  # Creates a new float 5.14
    ```
    
* **Removal**:
    
    * Not applicable for floats.
        
* **Traversal**:
    
    * Not applicable because floats are single values, not collections.
        
* **Hashable**:
    
    * Floats are hashable as they are immutable.
        
    * Example:
        
        ```python
        x = 3.14
        print(hash(x))  # Outputs the hash value of the float
        ```
        

---

### **What Does "Hashable" Mean?**

* **Definition**:
    
    * An object is hashable if it has a **hash value** (a number that represents the object) that remains constant during its lifetime.
        
    * Hashable objects can be used as keys in dictionaries or as elements in sets.
        
    * Mutability usually makes an object unhashable because changes would alter its hash value.
        
* **Rules for Hashability**:
    
    * The object must be **immutable**.
        
    * It must implement the `__hash__()` method.
        
    * It must implement the `__eq__()` method to compare equality.
        
* **Hashable Examples**:
    
    * Immutable types: `int`, `float`, `str`, `tuple` (if all elements are hashable).
        
    * Example:
        
        ```python
        d = {("a", 1): "value"}  # Tuples used as keys
        print(d[("a", 1)])  # Outputs: "value"
        ```
        
* **Unhashable Examples**:
    
    * Mutable types like `list`, `set`, and `dict` are not hashable.
        
    * Example:
        
        ```python
        l = [1, 2, 3]
        # print(hash(l))  # TypeError: unhashable type: 'list'
        ```
        

---

### Summary Table for Immutable Types

| **Data Type** | **Addition** | **Removal** | **Traversal** | **Hashable?** |
| --- | --- | --- | --- | --- |
| **Tuple** | Concatenation (`+`) | Create a new tuple | `for item in tuple` | Yes (if elements are hashable) |
| **String** | Concatenation (`+`) | Create a new string | `for char in string` | Yes |
| **Integer** | Create a new integer | Not applicable | Not applicable | Yes |
| **Float** | Create a new float | Not applicable | Not applicable | Yes |

## When to use what

* Use **list** if you need a dynamic collection and order matters.
    
* Use **tuple** if the data is constant and should not be modified.
    
* Use **dictionary** for key-value associations with efficient lookups.
    
* Use **set** for unique elements or when performing set operations.
