Writing a Python list to CSV takes three lines with the built-in csv module:
import csv
with open("out.csv", "w", newline="") as f:
csv.writer(f).writerow(["apple", "pear", "plum"])
That newline="" is not decoration. Leave it out and every row in your file gets a blank line after it, which is the most common complaint about CSV in Python.
What shape the data lands in depends on which method you call, so this guide walks through a flat list, a column, a list of lists and a list of dictionaries. Runs are on Python 3.12.5.

Write a flat list to one row
writerow() takes a single row and spreads the list across it:
import csv
fruits = ["apple", "pear", "plum"]
with open("fruits.csv", "w", newline="") as f:
writer = csv.writer(f)
writer.writerow(fruits)
print("file contents:")
print(open("fruits.csv").read())
print("one call to writerow puts the whole list across a single row.")
Output:
file contents:
apple,pear,plum
one call to writerow puts the whole list across a single row.

writerow() call puts the whole list on one line.The with block closes the file for you, which matters because CSV content is buffered and may not reach disk until the file closes.
Write a list to a CSV column
For one item per line, wrap each item in its own list:
import csv
fruits = ["apple", "pear", "plum"]
with open("column.csv", "w", newline="") as f:
writer = csv.writer(f)
for fruit in fruits:
writer.writerow([fruit])
print("file contents:")
print(open("column.csv").read())
print("each item is wrapped in its own list, so each becomes its own row.")
print()
print("the square brackets are the whole trick: writerow expects a ROW,")
print("and a row is a sequence of cells.")
Output:
file contents:
apple
pear
plum
each item is wrapped in its own list, so each becomes its own row.
the square brackets are the whole trick: writerow expects a ROW,
and a row is a sequence of cells.
A row is a sequence of cells, so ["apple"] is a row with one cell in it. Passing the bare string instead is exactly the mistake covered two sections down.
Write a list of lists to CSV
This is the normal case for tabular data, and writerows() handles it in one call:
import csv
table = [
["name", "quantity", "price"],
["apple", 3, 0.55],
["pear", 5, 0.80],
["plum", 12, 0.25],
]
with open("table.csv", "w", newline="") as f:
csv.writer(f).writerows(table)
print("file contents:")
print(open("table.csv").read())
print("writerows takes a list of lists and writes one row per inner list.")
Output:
file contents:
name,quantity,price
apple,3,0.55
pear,5,0.8
plum,12,0.25
writerows takes a list of lists and writes one row per inner list.
Each inner list becomes a row and each item inside it becomes a cell. Numbers are converted to text automatically, so mixing strings and floats needs no preparation.
Putting the header in as the first inner list is the simplest approach. Splitting a list into chunks first is useful when one file per batch is wanted.
writerow vs writerows
Mixing these two up produces a file that looks bizarre rather than an error:
import csv
fruits = ["apple", "pear", "plum"] # a FLAT list
with open("oops.csv", "w", newline="") as f:
csv.writer(f).writerows(fruits) # writerowS, by mistake
print("what you get:")
print(open("oops.csv").read())
print("every string was treated as a row, and a string is a sequence")
print("of characters, so each letter landed in its own cell.")
print()
print("writerow takes ONE row: writer.writerow(fruits)")
print("writerows takes MANY rows: writer.writerows([fruits])")
Output:
what you get:
a,p,p,l,e
p,e,a,r
p,l,u,m
every string was treated as a row, and a string is a sequence
of characters, so each letter landed in its own cell.
writerow takes ONE row: writer.writerow(fruits)
writerows takes MANY rows: writer.writerows([fruits])

writerows() on a flat list treats each string as a row of characters.writerows() expects a list of rows. Given a list of strings it treats each string as a row, and since strings are sequences of characters, every letter gets its own cell.
The rule is short: writerow(my_list) for one row, writerows(list_of_lists) for many.
Fixing extra blank lines in the CSV file
Every other row coming out blank is a Windows line ending problem:
import csv
table = [["name", "qty"], ["apple", 3], ["pear", 5]]
# opened WITHOUT newline=""
with open("doubled.csv", "w") as f:
csv.writer(f).writerows(table)
# opened correctly
with open("clean.csv", "w", newline="") as f:
csv.writer(f).writerows(table)
print("raw bytes without newline='':")
print(" ", open("doubled.csv", "rb").read())
print("raw bytes with newline='':")
print(" ", open("clean.csv", "rb").read())
print()
print("lines counted by the file itself:")
print(" doubled.csv ->", len(open("doubled.csv").readlines()))
print(" clean.csv ->", len(open("clean.csv").readlines()))
print()
print("the csv module writes its own line ending, and text mode on Windows")
print("turns that into a second one. newline='' stops the translation.")
Output:
raw bytes without newline='':
b'name,qty\r\r\napple,3\r\r\npear,5\r\r\n'
raw bytes with newline='':
b'name,qty\r\napple,3\r\npear,5\r\n'
lines counted by the file itself:
doubled.csv -> 6
clean.csv -> 3
the csv module writes its own line ending, and text mode on Windows
turns that into a second one. newline='' stops the translation.

newline="" the bytes show a doubled carriage return.The csv module writes its own line terminator. Text mode on Windows then translates the newline inside it a second time, giving the doubled carriage return visible in the bytes above.
newline="" turns that translation off and is the documented way to open a file for CSV. Include it on every open() you pass to csv.writer, on every platform, so the code behaves the same everywhere.
Write a list of dictionaries with DictWriter
When each item is a record rather than a row, DictWriter keeps the columns lined up:
import csv
people = [
{"name": "Ada", "role": "engineer", "city": "Seattle"},
{"name": "Grace", "role": "admiral", "city": "New York"},
]
with open("people.csv", "w", newline="") as f:
writer = csv.DictWriter(f, fieldnames=["name", "role", "city"])
writer.writeheader()
writer.writerows(people)
print("file contents:")
print(open("people.csv").read())
print("a key you forgot to declare is an error, not a silent drop:")
try:
with open("bad.csv", "w", newline="") as f:
writer = csv.DictWriter(f, fieldnames=["name"])
writer.writeheader()
writer.writerow({"name": "Ada", "role": "engineer"})
except ValueError as err:
print(" ValueError:", err)
print()
print("pass extrasaction='ignore' if you would rather skip the extra keys.")
Output:
file contents:
name,role,city
Ada,engineer,Seattle
Grace,admiral,New York
a key you forgot to declare is an error, not a silent drop:
ValueError: dict contains fields not in fieldnames: 'role'
pass extrasaction='ignore' if you would rather skip the extra keys.

DictWriter writes the header for you and rejects undeclared keys.fieldnames fixes both the column order and the header line, so dictionaries whose keys are in different orders still line up correctly.
A key you did not declare raises ValueError rather than disappearing quietly. That is usually what you want, though extrasaction="ignore" is there when it is not. See also converting a dictionary to a list.
Commas and quotes inside your data
This is the reason to use the module rather than joining strings yourself:
import csv
messy = ["Smith, John", 'he said "hi"', "two\nlines", ""]
with open("quoted.csv", "w", newline="") as f:
csv.writer(f).writerow(messy)
print("raw bytes:")
print(" ", open("quoted.csv", "rb").read())
print()
print("the comma inside a value, the quotes and even the line break")
print("were all handled without any escaping on your part.")
print()
print("this is why writing CSV by hand with ','.join() goes wrong:")
print(" ", ",".join(str(v) for v in messy).replace("\n", "<newline>"))
print(" that line now has four commas and would read back as five columns.")
Output: