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)

List Comprehensions in Python

You've seen a few list comprehension examples scattered through earlier articles in this series — this one covers the syntax properly, from the basic pattern through nested comprehensions, dict and set variants, and — just as important — when a comprehension is actually the wrong tool for the job.

Introduction: A Concise Way to Build Lists

A python list comprehension is a compact syntax for building a list by applying an expression to every item in an iterable, all in a single line. It replaces a pattern you've likely already written many times: create an empty list, loop over something, append to the list on each iteration.

# Traditional loop
squares = []
for x in range(5):
    squares.append(x ** 2)

# Equivalent list comprehension
squares = [x ** 2 for x in range(5)]

print(squares)   # [0, 1, 4, 9, 16]

Basic syntax

[expression for item in iterable]

Read left to right: for each item in iterable, compute expression, and collect all the results into a new list.

A brief historical note

List comprehension syntax was introduced in Python 2.0 through PEP 202, and it draws direct inspiration from set-builder notation in mathematics — the same notation behind writing something like "the set of all x-squared, for x in this range." That mathematical heritage is part of why the syntax reads in an unusual order compared to a typical loop (expression first, then the iteration) — it's mirroring how mathematicians have written this kind of definition for a long time before Python existed.

Filtering and Transforming with Conditionals

Filtering with a trailing if

Adding an if clause at the end of a comprehension filters which items get included — only items where the condition is True make it into the result:

sentence = "the quick brown fox"
vowels = [char for char in sentence if char in "aeiou"]
print(vowels)   # ['e', 'u', 'i', 'o']

Here, every character is checked against the condition, and only the vowels pass through into the final list.

Conditional expressions for transforming values

This is a genuinely common point of confusion: there's a second, different way if shows up in comprehensions — as a full conditional expression (the ternary operator, covered in the earlier if/elif/else article), placed before the for clause rather than after it. This version doesn't filter anything out — it transforms every item differently based on a condition, but keeps all of them in the result.

numbers = [-5, 3, -2, 8, -1]
n [n if n >= 0 else 0 for n in numbers]
print(non_negative)   # [0, 3, 0, 8, 0]
Why the position matters

This is worth being explicit about, since the two forms look deceptively similar:

# Filtering — if AFTER the for clause, no else — excludes items
evens = [n for n in range(10) if n % 2 == 0]

# Transforming — if/else BEFORE the for clause — keeps all items, changes some
labeled = [n if n % 2 == 0 else -1 for n in range(10)]

The rule of thumb: if at the end, with no else, filters. if/else positioned right after the expression, before for, transforms every item without removing any.

Nested Comprehensions and Dict/Set Variants

Multiple for clauses

Comprehensions can include more than one for clause, functioning like nested loops — genuinely useful for flattening a matrix (a list of lists) into a single flat list:

matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

flattened = [num for row in matrix for num in row]
print(flattened)   # [1, 2, 3, 4, 5, 6, 7, 8, 9]

The for clauses execute left to right, in the exact order they appear — this reads the same order as an equivalent nested loop would run:

# The nested loop equivalent, for comparison
flattened = []
for row in matrix:
    for num in row:
        flattened.append(num)

The first for clause in the comprehension corresponds to the outer loop, and each subsequent for clause corresponds to progressively deeper nested loops.

The same bracket-swap pattern for dicts and sets

The comprehension pattern isn't exclusive to lists — swapping [] for {} (with the right internal syntax) gives you dictionary and set comprehensions, both covered briefly in earlier articles:

# Dictionary comprehension — note the key: value syntax
squares_dict = {n: n ** 2 for n in range(5)}
print(squares_dict)   # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

# Set comprehension — same syntax as a list comprehension, but with {}
unique_lengths = {len(word) for word in ["cat", "dog", "fish", "ox"]}
print(unique_lengths)   # {3, 4, 2} — duplicates (two 3-letter words) automatically collapsed
Practical examples

Inverting a dictionary:

original = {"a": 1, "b": 2, "c": 3}
inverted = {value: key for key, value in original.items()}
print(inverted)   # {1: 'a', 2: 'b', 3: 'c'}

Building a word-length lookup:

words = ["cat", "elephant", "dog", "hippopotamus"]
lengths = {word: len(word) for word in words}
print(lengths)   # {'cat': 3, 'elephant': 8, 'dog': 3, 'hippopotamus': 12}

Deduplicating with a set comprehension:

numbers = [1, 2, 2, 3, 3, 3, 4]
unique = {n for n in numbers}
print(unique)   # {1, 2, 3, 4}

(In that last example specifically, set(numbers) would be simpler and equally effective — the comprehension form earns its place once you need to transform each item while deduplicating, not just deduplicate as-is.)

Performance and Readability Tradeoffs

Why comprehensions are typically faster

List comprehensions are generally faster than the equivalent explicit loop with repeated .append() calls. At the bytecode level, Python optimizes comprehensions using a specialized internal operation for building the list, rather than repeatedly looking up and calling the .append() method on every single iteration the way a manual loop does. For small lists this difference is negligible, but it can add up meaningfully over large datasets or tight loops.

The readability litmus test

Speed isn't the only consideration, and it's often not even the deciding one. A good practical test: if a comprehension takes more than a couple of seconds to mentally parse, it's a sign to refactor it into a full loop or a dedicated helper function instead. Comprehensions are meant to make code more readable, not less — the moment that stops being true for a particular piece of logic, the comprehension has outlived its usefulness there.

# Getting hard to parse at a glance
result = [x * 2 for x in [y ** 2 for y in range(10) if y % 2 == 0] if x > 10]

# Clearer, even though it's more lines
evens_squared = [y ** 2 for y in range(10) if y % 2 == 0]
result = [x * 2 for x in evens_squared if x > 10]

Both produce the same output, but splitting the logic into two named steps makes it dramatically easier to follow what each part is actually doing.

Style guide rule of thumb

Several widely used Python style guides — Google's internal style guide among them — recommend keeping each part of a comprehension (the expression, the for clause, and any filter) to roughly one line each, and generally avoiding stacking more than one for or if clause in a single comprehension. Once you're nesting multiple loops and multiple conditions together, that's usually the signal to break the logic apart, exactly as shown above.

When to Use a Loop (or Generator) Instead

Skip comprehensions for side effects

Comprehensions exist to build a collection — they're not meant to be used purely for their side effects, like printing or writing to a file. Using one this way technically works but reads as a misuse of the syntax, and often produces a confusing, throwaway result you didn't actually want:

# Misuse — building a list of None values just to trigger print() as a side effect
[print(x) for x in range(5)]   # works, but wasteful and unclear

# Correct — a plain loop, since there's no list being built for later use
for x in range(5):
    print(x)

The same applies to complex, multi-step logic, or anything requiring a try/except block inside the loop body — comprehensions don't support exception handling gracefully, and forcing it in tends to produce something far less readable than a straightforward loop.

Generator expressions: comprehensions without building a full list

If you need to iterate over the results just once, and don't need them stored as an actual list in memory, a generator expression — identical syntax, but with parentheses instead of square brackets — is often the better choice:

squares_list = [x ** 2 for x in range(1000000)]     # builds the entire list in memory, immediately
squares_gen = (x ** 2 for x in range(1000000))        # produces values one at a time, on demand

A generator computes each value only when it's actually asked for, rather than computing and storing everything up front. For a huge dataset, or any situation where you're just going to loop through the results once (say, feeding them into sum() or another loop), this can be a significant memory saving over building the full list first.

Quick decision checklist
  • Need an actual list, the logic is simple, and the dataset is a reasonable size → list comprehension.

  • Need a dict or set built from an expression → dict/set comprehension.

  • Involves side effects, complex multi-step logic, or exception handling → a regular loop.

  • Iterating a huge dataset, or only need to pass through the results once → a generator expression.

PREVIOUSNEXT LESSON