DevHireLab
Tutorials
BootcampProblemsCode SimulatorAI InterviewSoonContact
DevHireLab
Tutorials
BootcampProblemsCode SimulatorAI InterviewSoonContact
Getting Started
01
What is Python and Why Learn It in 2026
02
Installing Python on Windows, Mac, and Linux
03
Setting up VS Code / PyCharm for Python
04
Understanding the Python Interpreter and REPL
05
Writing and Running Your First Python Program
06
Python Syntax and Indentation Rules
Variables and Data Types
01
Variables and Naming Conventions in Python
02
Numbers in Python (int, float, complex)
03
Strings and String Methods in Python
04
Booleans in Python
05
Type Conversion / Casting in Python
06
Understanding the None Type in Python
Operators
01
Arithmetic Operators in Python
02
Comparison Operators in Python
03
Logical Operators in Python
04
Assignment Operators in Python
05
Bitwise Operators in Python
06
Identity (is) and Membership (in) Operators
07
Walrus Operator (:=) in Python
Input, Output & Strings
01
input() and print() Functions in Python
02
f-strings and String Formatting in Python
03
String Slicing and Indexing in Python
04
Common String Methods (split, join, strip, replace)
05
Escape Characters in Python Strings
Control Flow
01
if, elif, else Statements in Python
02
Nested Conditionals in Python
03
for Loops in Python
04
while Loops in Python
05
break, continue, and pass Statements
06
Loop with else Clause in Python
07
match-case (Structural Pattern Matching)
Data Structures
01
Python Lists: Creation, Indexing, Slicing, Methods
02
Python Tuples and Immutability
03
Python Sets and Set Operations
04
Python Dictionaries: Keys, Values, and Methods
05
List Comprehensions in Python
06
Dictionary & Set Comprehensions in Python
07
Working with Nested Lists and Dictionaries
Functions
01
Defining and Calling Functions in Python
02
Function Arguments: Positional, Keyword, Default
03
*args and **kwargs in Python
04
Return Statements in Python Functions
05
Lambda Functions in Python
06
Recursion in Python
07
Variable Scope: Local, Global, Nonlocal
08
Docstrings and Function Annotations
Functional Programming Basics
01
map(), filter(), and reduce() in Python
02
Closures in Python Explained
03
Python Decorators: Basic to Advanced
04
Generators and yield in Python
Object-Oriented Programming (OOP)
01
Classes and Objects in Python
02
The init Constructor in Python
03
Instance vs Class Variables in Python
04
Inheritance in Python (Single, Multiple, Multilevel)
05
Polymorphism in Python
06
Encapsulation in Python
07
Abstraction in Python with the abc Module
08
Magic/Dunder Methods in Python
09
Static and Class Methods in Python
10
The @property Decorator in Python
11
Python Dataclasses Explained
Error Handling
01
try, except, finally in Python
02
Handling Multiple Exceptions in Python
03
Raising Custom Exceptions in Python
04
Understanding Python's Exception Hierarchy
05
Assertions in Python
File Handling
01
Reading and Writing Text Files in Python
02
Working with the with Statement in Python
03
Working with CSV Files in Python
04
Working with JSON Files in Python
05
Working with Directories (os, pathlib) in Python
Modules and Packages
01
Importing Built-in Modules in Python
02
Creating Your Own Modules in Python
03
Understanding Packages and init.py
04
Python Standard Library Overview
05
Installing Packages with pip
Advanced Core Concepts
01
Iterators and Iterables in Python
02
Context Managers in Python (with, contextlib)
03
Multithreading Basics in Python
04
Multiprocessing Basics in Python
05
Asyncio Basics: async/await in Python
06
Memory Management and Garbage Collection in Python
07
Type Hints in Python (typing module)

Identity (is) and Membership (in) Operators

Python actually gives you three distinct ways to compare things, and they each answer a genuinely different question. == asks "do these have the same value?" The python identity operator, is, asks "are these literally the same object in memory?" And the python membership operator, in, asks "does this value exist somewhere inside this collection?" This article covers the last two — identity and membership — including the mix-up between is and == that catches even developers with some experience.

Two Different Kinds of Comparison

To set the stage, here's the distinction in one line each:

  • == — compares values. Are these two things equal?

  • is — compares identity. Are these two names pointing to the exact same object?

  • in — checks membership. Does this value exist somewhere inside this collection?

The rest of this article walks through in/not in first, then is/is not, and finishes with the specific mistake that trips people up when they confuse identity with equality.

Membership Operators: in and not in

in

in checks whether a value exists within a sequence — a list, tuple, string, set, or dictionary.

fruits = ["apple", "banana", "cherry"]
print("banana" in fruits)   # True
print("grape" in fruits)    # False
not in

not in is the inverse — it checks that a value does not exist within a collection:

print("grape" not in fruits)   # True
Across different collection types

Membership checks work consistently across Python's various sequence and collection types:

# Strings — checks for a substring
sentence = "the quick brown fox"
print("quick" in sentence)   # True

# Tuples
coordinates = (1, 2, 3)
print(2 in coordinates)      # True

# Sets
unique_ids = {101, 102, 103}
print(104 in unique_ids)     # False
A note on dictionaries

Checking in on a dictionary checks its keys by default, not its values:

user = {"name": "Alex", "age": 30}

print("name" in user)     # True  — 'name' is a key
print("Alex" in user)     # False — 'Alex' is a value, not a key

If you specifically need to check values, you'd check against .values() instead: "Alex" in user.values().

Identity Operators: is and is not

is

is checks whether two variables reference the exact same object in memory — not merely equal values, but the literal same object.

a = [1, 2, 3]
b = [1, 2, 3]

print(a == b)   # True  — same values
print(a is b)   # False — two separate objects, even though they look identical

a and b are two independently created lists that happen to contain the same values. They're equal, but they are not the same object.

is not

is not is the inverse:

print(a is not b)   # True — confirms they're different objects
Using id() to see what's really happening

Python's built-in id() function returns a unique identifier representing an object's location in memory. It's a useful way to see the identity distinction directly, rather than taking it on faith:

a = [1, 2, 3]
b = [1, 2, 3]

print(id(a))   # some number, e.g. 140234881672384
print(id(b))   # a different number
print(id(a) == id(b))   # False — confirms they're different objects

If, instead, you assign one variable to another directly, both names point to the same object, and is correctly reflects that:

a = [1, 2, 3]
c = a   # c now refers to the same list object as a

print(a is c)          # True
print(id(a) == id(c))  # True

is vs. ==: Avoiding a Common Mistake

The core distinction, restated

== compares values. is compares identity. These usually agree for immutable values you'd expect to be equal, and usually diverge for separately created mutable objects — which is exactly what makes the mistake so easy to make without noticing.

Why is can appear to "work" — and why that's unreliable

Here's where things get genuinely confusing. For small integers and short strings, Python's implementation (CPython) reuses cached objects for performance reasons, which can make is appear to behave like == in specific cases:

a = 5
b = 5
print(a is b)   # True — small integers are cached and reused by CPython

x = "hello"
y = "hello"
print(x is y)   # True in most cases — short strings are often interned

This looks like is is working fine as a substitute for == — until it isn't:

a = 500
b = 500
print(a is b)   # Often False — outside CPython's small-integer cache range

This behavior is a CPython implementation detail, not a guaranteed language feature. It can differ across Python versions, other Python implementations, or even different contexts within the same program. Relying on it is a bug waiting to surface later, on a different system or a slightly different value.

Best practice

Use is/is not only for singleton checks — most importantly, checking against None:

if value is None:
    print("No value provided")

Use == for everything else — numbers, strings, lists, dictionaries, custom objects, all of it. This isn't a stylistic preference; it's the distinction that keeps your code behaving predictably regardless of which Python implementation or version it happens to run on.

Practical Use Cases and Best Practices

Membership operators in practice

in and not in show up constantly in everyday validation and searching:

allowed_roles = ["admin", "editor", "viewer"]
user_role = "editor"

if user_role in allowed_roles:
    print("Access permitted")
else:
    print("Access denied")

They're just as useful for filtering data — checking whether an item should be included based on membership in some reference collection:

blocked_words = {"spam", "scam", "phishing"}
message = "this looks like spam"

c any(word in message for word in blocked_words)
print(contains_blocked_word)   # True
Identity operators in practice

Beyond None checks, is is genuinely useful for confirming whether two variable names refer to the same mutable object — which matters for catching a specific category of bug: aliasing, where modifying one variable unexpectedly affects another because they secretly point to the same underlying object.

original = [1, 2, 3]
alias = original       # this does NOT create a copy
copy = original[:]     # this DOES create a copy

alias.append(4)
print(original)   # [1, 2, 3, 4] — changed, because alias points to the same list
print(original is alias)   # True — confirms the shared reference

copy.append(5)
print(original)   # [1, 2, 3, 4] — unchanged, because copy is a separate object
print(original is copy)   # False — confirms they're different objects

Understanding is here isn't just academic — it's the tool that lets you diagnose exactly why one part of your program appears to be "mysteriously" affecting another part that looks unrelated.

Readability tip

When combining membership and identity checks in the same condition, parentheses make the intent much clearer:

if (value in allowed_roles) and (other is not None):
    print("Both conditions satisfied")

Neither operator has particularly surprising precedence on its own, but grouping each check explicitly removes any ambiguity for whoever reads the code next — including you, coming back to it later.

PREVIOUSNEXT LESSON