Python map() Function: Transform Iterables Without Writing a Loop

Print the result of the Python map function and you get an address instead of a list, because nothing has run yet. The four numbers you passed in are still waiting inside an iterator, and the values only appear when something asks for them. That delay is the feature, since the same call handles four items and a file far larger than memory without changing shape, and reading that once-used iterator a second time gives you an empty list with no error.

Python map returns an iterator, not a list

Pass a doubling function and four numbers to map, then print the result without converting it.

numbers = [1, 2, 3, 4]
doubled = map(lambda n: n * 2, numbers)
print(doubled)
Terminal output showing the map object address printed for a doubling function over four numbers

The interpreter answers with a map object address. That address carries the whole lesson, because nothing has been multiplied yet.

Map stored the function next to the iterable and promised to call it later. The work runs element by element, only when something consumes the iterator.

That laziness lets the same call serve a short list and a file far larger than memory, holding one element at a time rather than the full result.

Consumption is single use, which surprises people who expect a list. Run the same conversion twice and watch the second call.

numbers = [1, 2, 3, 4]
doubled = map(lambda n: n * 2, numbers)
print(list(doubled))
print(list(doubled))
Terminal output showing the first list conversion returning doubled numbers and the second returning an empty list

The second list call returns nothing and raises nothing. Keep the materialized list when you need the values twice.

numbers = [1, 2, 3, 4]
result = list(map(lambda n: n * 2, numbers))
print(result)
print(result)

The kept list answers every later use. Treat the map object as a one-way pipe and the list as storage.

Lambda or a named function inside the map function

A lambda fits when the transformation is one short expression. Doubling four numbers needs nothing more than that.

print(list(map(lambda n: n * 2, [1, 2, 3, 4])))

The lambda lives only inside this call. Nobody else can reuse it, and tracebacks show no helpful name.

Give the same work a def with a docstring and the call reads better. Debugging improves because the function owns a name.

def double(n):
    """Double one number."""
    return n * 2
print(list(map(double, [1, 2, 3, 4])))
print(double.__doc__)

The second printed line is the docstring, reachable as usual. A named function also serves every later map call without duplication.

Reach for def once the logic needs a second line or a second caller. One-line throwaway logic stays a fair lambda.

The call passes the function itself, never its result. Add parentheses and Python calls the function at once with no argument.

def double(n):
    return n * 2
print(list(map(double(), [1, 2, 3, 4])))

The traceback reports that double is missing its argument n. When map complains about a missing argument, check for stray parentheses first.

Passing functions as arguments is the callback style, covered in depth in the callback functions in Python overview.

Python map with several iterables stops at the shortest

Map accepts more than one iterable when the function takes more than one argument. Pair each name with its score.

names = ['amy', 'ben', 'cat']
scores = [10, 20, 30]
print(list(map(lambda n, s: (n, s), names, scores)))

Each call receives one element from each iterable in step. The pairing mirrors zip without building tuples first.

The function signature decides how many iterables map consumes. A two-argument function over one iterable starves, as the failure section shows.

Unequal lengths end the iteration at the shorter input. The leftover tail never runs.

names = ['amy', 'ben', 'cat', 'dan']
scores = [10, 20]
print(list(map(lambda n, s: (n, s), names, scores)))

Silent drops corrupt reports, so this behavior deserves a guard. Dan has no score here and his row vanishes without warning.

An assertion before the call turns the silent drop into a loud failure. Length checks belong with every multi-iterable map over messy inputs.

names = ['amy', 'ben', 'cat', 'dan']
scores = [10, 20]
assert len(names) == len(scores), f'length mismatch: {len(names)} names vs {len(scores)} scores'
print(list(map(lambda n, s: (n, s), names, scores)))

The AssertionError names both lengths. Fix the data or decide the pairing rule before rerunning.

This rule also explains why generators pair well with map. Both sides stay lazy, so a large file joins another large file without staging either one.

Worked example: cleaning and typing CSV rows

A reader export arrives as padded strings with missing fields and a stray word where a number belongs. Four rows stand in for the full file.

rows = ["  alice, 29, 52000 ", "BOB,31,61000", " cara , , 48000", "dan,27,not-a-number "]
for r in rows:
    print(repr(r))

Every row needs the same treatment, so strip each field and split each row instead of hand-editing four rows.

rows = ["  alice, 29, 52000 ", "BOB,31,61000", " cara , , 48000", "dan,27,not-a-number "]
cells = [list(map(str.strip, r.split(","))) for r in rows]
for c in cells:
    print(c)

BOB keeps his case here because typing comes before formatting. Empty strings and stray words survive this pass on purpose.

Conversion needs a fallback for blanks and garbage. A small named function beats a lambda once try and except enter the picture.

def to_int(text, fallback=0):
    try:
        return int(text)
    except ValueError:
        return fallback
raw = ["29", "31", "", "not-a-number"]
print(list(map(to_int, raw)))

Blanks and stray words land on zero while valid numbers convert. The caller later decides what zero means in context.

Assemble the cleaned cells into records with capitalized names. Later sections reuse these records instead of inventing new data.

def to_int(text, fallback=0):
    try:
        return int(text)
    except ValueError:
        return fallback
rows = ["  alice, 29, 52000 ", "BOB,31,61000", " cara , , 48000", "dan,27,not-a-number "]
cells = [list(map(str.strip, r.split(","))) for r in rows]
records = [{"name": c[0].capitalize(), "age": to_int(c[1]), "salary": to_int(c[2])} for c in cells]
for r in records:
    print(r)
Terminal output showing four cleaned record dicts with capitalized names and typed age and salary values

The dicts now carry typed values built from messy strings. The same passes scale to the full file unchanged.

Where the map object can go besides a list

List is only the most familiar consumer. Tuple and set take the iterator directly.

print(tuple(map(lambda n: n * 2, [1, 2, 3])))
print(set(map(lambda w: w.lower(), ['Amy', 'amy', 'BEN', 'ben'])))

The set keeps one spelling of each name. Duplicates collapse during consumption, not in a separate pass.

Reductions like sum and max never build a collection at all. Converted values flow into the arithmetic.

print(sum(map(int, ['10', '20', '30'])))
print(max(map(len, ['amy', 'benjamin', 'cat'])))

Each element converts on arrival, then leaves memory. Sixty is the sum of the converted strings and eight is the longest name length.

Join consumes an iterator too, but every item must be a string. Integers stop it with a TypeError.

print(', '.join([1, 2, 3]))

The message names the offending item type. Convert with str through map and the same join succeeds.

print(', '.join(map(str, [1, 2, 3])))

This pair appears often when printing numeric results. Chaining works as well, since one map object feeds the next consumer.

print(sorted(map(lambda w: w.strip().capitalize(), ['  ben', 'amy ', ' cat '])))
print(list(map(abs, map(int, ['-3', '4', '-5']))))

The sorted list and the absolute values each come from a single pass. No intermediate list exists between stages.

Feeding map into another consumer skips storage entirely. Choose this shape when the result leaves the program through print, a file write, or a network call.

Map or a list comprehension: the decision rule

I timed both forms over twenty thousand short strings with fifty rounds each, and map finished in 0.097 seconds against 0.080 for the comprehension on this server.

import timeit
setup = 'nums = list(range(20000))'
t_map = timeit.timeit('list(map(str, nums))', setup=setup, number=50)
t_comp = timeit.timeit('[str(n) for n in nums]', setup=setup, number=50)
print(f'map: {t_map:.3f}s')
print(f'comprehension: {t_comp:.3f}s')
Terminal output comparing map and list comprehension timing over twenty thousand strings

Readability decides far more calls than this timing ever will. The difference is noise for most programs.

Comprehensions express filtering directly. Map has no slot for a condition, so selection needs help.

nums = list(range(10))
print([n * 2 for n in nums if n % 2 == 0])

Even numbers double while odd numbers disappear. Reaching that shape with map takes filter or a filler lambda, both harder to read.

Map shines when a named function already exists and no condition applies. Stripping a field list needs no new syntax at all.

raw = ["  amy ", " ben", "cat  "]
print(list(map(str.strip, raw)))

Str strip passes as the function with no wrapper. The comprehension equivalent spells out the same call with more words.

Flattening pairs takes one expression where map needs two passes or a helper. Nesting also favors the comprehension.

pairs = [[1, 2], [3, 4]]
print([n * 10 for row in pairs for n in row])

The rule stays short. Filter or nest with a comprehension, and map with an existing function and no condition.

The list comprehension tutorial covers filtering, nesting, and conditional expressions in full.

Failure boundaries worth knowing before they find you

None is not a default function for map. Python 2 allowed it as a zip stand-in and Python 3 refuses.

print(list(map(None, [1, 2, 3])))

The TypeError says None is not callable. Pair iterables with zip when you need that old shape.

For both failures in this section, read the first argument and the function signature before suspecting the data.

Arity cuts the other way too. A two-argument function over one iterable starves its second parameter.

print(list(map(lambda a, b: a + b, [1, 2, 3])))

The traceback names the starved parameter b. Count the function arguments against the iterables before looking further.

Convert a batch with one bad entry and watch what survives. Exceptions mid-iteration destroy the progress so far.

def strict_int(text):
    return int(text)
conv = map(strict_int, ["10", "20", "xx", "30"])
try:
    print(list(conv))
except ValueError as e:
    print(f"ValueError: {e}")
print(list(conv))

The first consumption raises on xx and returns nothing. A second consumption resumes past the failure and yields only 30.

Validate or wrap conversion before the map call when partial results matter. Laziness and error recovery mix poorly.

Laziness also bounds memory. A ten-million-range map object occupies 48 bytes while a ten-item list needs 136.

import sys
nums = range(10_000_000)
m = map(str, nums)
print(type(m))
print(sys.getsizeof(m))
print(sys.getsizeof(list(range(10))))

The iterator never stages the range. Materialize only the slice you keep.

What map is the wrong tool for

Selection reads badly through map. A filler lambda returns None for every rejected item instead of skipping it.

nums = list(range(10))
print(list(map(lambda n: n * 2 if n % 2 == 0 else None, nums)))

The Nones preserve positions but force every later reader to filter them. Filter or a comprehension states the intent directly.

Position-preserving placeholders help only in narrow numeric code. In record cleaning like the CSV task, a None salary poisons averages and counts alike.

Map builds values, so printing through it computes on demand and confuses ordering. Side effects suffer more.

nums = [1, 2, 3]
pending = map(print, nums)
print('map object created, nothing printed yet')
list(pending)
print('consumed')

Nothing prints until list forces the iterator. A plain loop shows the effect at once and ends the cleverness there.

Transformation is the boundary. When each input produces exactly one output with no condition and no effect, map fits.

Anything else wants a dedicated construct, with conditions in filter and comprehensions and effects in loops.

Keep map for pure element-wise transforms. Your future reader understands the call at first glance.

Frequently asked questions

What does map do in Python?

Map applies one function to every item in an iterable and returns a lazy iterator with the results. Nothing runs until something consumes that iterator, and each element transforms on demand. Convert the iterator with list, tuple, or set when you need stored results.

How do you use map in Python?

Call map with a function and an iterable, then consume the result, as in list(map(str.strip, fields)). Pass the function itself, since parentheses would call it early with no argument. Multiple iterables pair element-wise and stop at the shortest.

What is a map object in Python?

The map object is the lazy iterator that map returns before any work runs. It holds the function next to the input iterable and computes each element only when consumed. It is single use, so materialize it with list when the values serve twice.

How does map work with several iterables in Python?

Map feeds one element from each iterable into each call, stopping when the shortest input runs out. A two-argument function needs exactly two iterables, and longer tails drop silently. Assert equal lengths first when every row must pair.

When should you choose a list comprehension over map in Python?

Choose the comprehension when filtering, nesting, or combining a condition with a transform. Choose map when a named function already exists and every element transforms without a condition. Timing differences between the two stay small enough that readability decides.

Ninad
Ninad

A Python and PHP developer turned writer out of passion. Over the last 6+ years, he has written for brands including DigitalOcean, DreamHost, Hostinger, and many others. When not working, you'll find him tinkering with open-source projects, vibe coding, or on a mountain trail, completely disconnected from tech.

Articles: 136