How to Write to a CSV File in Python: Absolute Beginners Tutorial

Final Exam & Certification

Complete this tutorial and pass the 10-question final exam to earn a downloadable certificate of completion.

skip to exam

CSV files are one of the most common ways to store and share tabular data. Python makes writing them straightforward with its built-in csv module — no third-party packages required. This tutorial covers everything a beginner needs to write CSV files confidently in Python.

Before writing a single line of code, it helps to understand what a CSV file actually is and why Python's standard library already has everything you need to create one.

What Is a CSV File?

CSV stands for Comma-Separated Values. A CSV file is a plain text file where each line represents a row of data, and the values within each row are separated by commas. Spreadsheet applications like Excel and Google Sheets can open CSV files directly, which makes them a practical choice for exporting and sharing data.

Here is what a basic CSV file looks like when you open it in a text editor:

csv
name,age,city
Alice,30,New York
Bob,25,Chicago
Carol,35,Austin

The first row is typically the header row — the column names. Every row that follows is a data row. The csv module handles all the formatting details for you, including quoting values that contain commas or special characters.

Note

Python's csv module is part of the standard library. You do not need to install anything with pip. Just add import csv at the top of your file and you are ready.

The csv Module and csv.writer

The most direct way to write a CSV file is with csv.writer. You open a file, pass the file object to csv.writer(), and then call writerow() with a list of values for each row you want to write.

Here is the simplest possible example:

python
import csv

with open('output.csv', 'w', newline='') as file:
    writer = csv.writer(file)
    writer.writerow(['name', 'age', 'city'])
    writer.writerow(['Alice', 30, 'New York'])
    writer.writerow(['Bob', 25, 'Chicago'])

After running this script, a file called output.csv will appear in the same directory as your script. It will contain three rows: the header and two data rows.

Why newline='' Matters

The newline='' argument in open() is essential on Windows. Without it, Python applies its own newline handling on top of what the csv module produces, which results in an extra blank line between every row. Passing newline='' disables that translation and lets the csv module manage line endings on its own. It is good practice to always include this argument regardless of your operating system.

Common Mistake

Forgetting newline='' when opening the file on Windows will produce blank lines between every row in your CSV. Always include it when working with the csv module.

Using with Statements

The with statement is the recommended way to open files in Python. It guarantees the file is closed properly when the block ends, even if an error occurs inside the block. You should always use it when writing CSV files rather than calling open() and file.close() separately.

code builder click a token to place it

Build the correct line that opens a CSV file for writing with proper newline handling:

your code will appear here...
newline='' open('data.csv', 'r' with as 'w', file: encoding='utf-8'
Why: The correct order is with open('data.csv', 'w', newline='') as file:. The mode 'w' opens for writing. The newline='' argument prevents extra blank lines. The 'r' token is a distractor — that mode opens for reading, not writing.

Writing Headers and Multiple Rows

Most CSV files start with a header row that names the columns. With csv.writer, you write the header row exactly like any other row — pass a list of column name strings to writerow().

If you have all your data ready in a list of lists, you can write every data row in a single call using writerows():

python
import csv

header = ['product', 'price', 'quantity']

rows = [
    ['Widget A', 9.99, 100],
    ['Widget B', 14.99, 50],
    ['Widget C', 4.99, 200],
]

with open('inventory.csv', 'w', newline='') as file:
    writer = csv.writer(file)
    writer.writerow(header)   # write the header row
    writer.writerows(rows)    # write all data rows at once

The difference between writerow() and writerows() is straightforward: writerow() writes one row from a single list, while writerows() writes multiple rows from a list of lists.

Pro Tip

Use writerows() when you have all your data collected in a list. Use writerow() when you are generating rows one at a time, such as inside a loop that processes data line by line.

Handling Commas and Special Characters in Values

If a value contains a comma, the csv module automatically wraps it in double quotes so the file remains valid. For example, writing ['New York, NY', 'United States'] produces "New York, NY",United States in the file. You do not need to do anything special — the module handles this for you.

check your understanding question 1 of 4

Using csv.DictWriter

csv.DictWriter is a higher-level tool that writes rows from dictionaries instead of lists. You specify the column names when you create the writer, and then pass a dictionary for each row. This approach is especially readable when your data is already stored as dictionaries — a common situation when working with APIs or database query results.

python
import csv

employees = [
    {'name': 'Alice', 'department': 'Engineering', 'salary': 95000},
    {'name': 'Bob',   'department': 'Marketing',   'salary': 72000},
    {'name': 'Carol', 'department': 'Engineering', 'salary': 102000},
]

fields = ['name', 'department', 'salary']

with open('employees.csv', 'w', newline='') as file:
    writer = csv.DictWriter(file, fieldnames=fields)
    writer.writeheader()          # writes the header row automatically
    writer.writerows(employees)   # writes all dictionary rows

The key difference from csv.writer is that DictWriter has a dedicated writeheader() method. Calling it writes the fieldnames you provided as the header row, so you do not have to write the header manually.

csv.writer vs csv.DictWriter at a Glance

Input type
List of values per row
Header
Write manually with writerow()
Best for
Simple data, already in list form
Method
writerow(), writerows()
Input type
Dictionary per row (keys = column names)
Header
Write automatically with writeheader()
Best for
Data from APIs, databases, or named records
Method
writeheader(), writerow(), writerows()
Use writer
When you control the column order and data is already a list
Use DictWriter
When your data has named fields and readability matters
Column order
Both respect the order you specify — writer via list order, DictWriter via fieldnames

Appending to an Existing CSV File

Opening a file in write mode ('w') creates a new file or overwrites an existing one. To add new rows to an existing CSV without destroying its contents, open the file in append mode using 'a' as the mode argument.

python
import csv

new_employee = {'name': 'Diana', 'department': 'Finance', 'salary': 88000}

fields = ['name', 'department', 'salary']

# 'a' opens in append mode — adds to the end of the file
with open('employees.csv', 'a', newline='') as file:
    writer = csv.DictWriter(file, fieldnames=fields)
    writer.writerow(new_employee)

Note that when appending, you do not call writeheader() again. The header is already in the file. Calling writeheader() in append mode would add a duplicate header row in the middle of your data.

Common Mistake

Do not call writeheader() when appending to a file that already has a header. It will insert a second header row in the middle of your data, which will corrupt the file structure.

Writing Numbers and Other Data Types

The csv module converts all values to strings before writing them. You can safely pass integers, floats, or booleans — they will be written as their string representations. When reading the file back, you will need to convert the strings to the appropriate types yourself (or use a library like pandas that handles this automatically).

python
import csv

# Mixed types — the csv module converts everything to string
with open('readings.csv', 'w', newline='') as file:
    writer = csv.writer(file)
    writer.writerow(['sensor', 'value', 'active'])
    writer.writerow(['temp_01', 23.7, True])
    writer.writerow(['temp_02', 19.2, False])

# Result in the file:
# sensor,value,active
# temp_01,23.7,True
# temp_02,19.2,False
code builder click a token to place it

Build the correct call that writes the header row using DictWriter:

your code will appear here...
csv.DictWriter(file, writer.writerow() writer fieldnames=fields) writer.writeheader() = csv.writer(file)
Why: You first create the writer with csv.DictWriter(file, fieldnames=fields), then call writer.writeheader() to write the column names. Using writer.writerow() instead of writeheader() is a distractor — writerow() exists on DictWriter too, but writeheader() is the dedicated method for writing the fieldnames as a header.

Beyond the Basics: Dialects, Encoding, and In-Memory CSV

Once you are comfortable with csv.writer and csv.DictWriter, there are several less commonly taught but highly practical techniques that solve real problems you will encounter in production code.

Custom Delimiters and the dialect Parameter

CSV is a loose standard. Some systems produce files with tabs, semicolons, or pipe characters as separators rather than commas. You can pass a delimiter argument directly to csv.writer to control this, or register a named dialect with csv.register_dialect and reuse it across multiple writers.

python
import csv

# Tab-separated values — useful for systems that reject commas
with open('report.tsv', 'w', newline='') as file:
    writer = csv.writer(file, delimiter='\t')
    writer.writerow(['name', 'score', 'grade'])
    writer.writerow(['Alice', 98, 'A'])
    writer.writerow(['Bob', 74, 'C'])

# Named dialect — register once, use everywhere
csv.register_dialect(
    'pipe_delimited',
    delimiter='|',
    quotechar='"',
    quoting=csv.QUOTE_MINIMAL,
)

with open('export.csv', 'w', newline='') as file:
    writer = csv.writer(file, dialect='pipe_delimited')
    writer.writerow(['id', 'product', 'price'])
    writer.writerow([1, 'Widget A', 9.99])

Controlling Quoting with the quoting Parameter

By default, the csv module uses csv.QUOTE_MINIMAL, which only quotes values that contain the delimiter, the quotechar, or a line terminator. You can change this behaviour with the quoting argument. The four quoting constants and when to use each are covered in the accordion below.

Behaviour
Only quotes values that contain the delimiter, quotechar, or a line terminator
Use when
General purpose — produces the most compact output
Example output
Alice,30,"New York, NY"
Behaviour
Quotes every field regardless of content
Use when
Sending data to systems with strict parsers, or when you want a completely unambiguous file
Example output
"Alice","30","New York"
Behaviour
Quotes all non-numeric fields; numeric values are written without quotes
Use when
You want the file to signal clearly which columns are strings vs numbers
Example output
"Alice",30,"New York"
Behaviour
Never quotes fields; raises an error if a value contains the delimiter unless escapechar is set
Use when
Writing a custom log format or fixed-structure output where quoting is never needed
Example output
Alice,30,New York (will error if a value contains a comma)

Encoding Non-ASCII Data

Python 3's open() defaults to the platform encoding, which varies by operating system. For any CSV that may contain non-ASCII characters — accented letters, Chinese characters, currency symbols — you should explicitly specify encoding='utf-8-sig' rather than encoding='utf-8'. The utf-8-sig variant writes a byte order mark (BOM) that tells Excel to open the file in UTF-8 mode automatically, which prevents the garbled characters that are a common frustration when sharing CSV files with Excel users.

python
import csv

# utf-8-sig writes a BOM so Excel opens the file correctly
with open('international.csv', 'w', newline='', encoding='utf-8-sig') as file:
    writer = csv.writer(file)
    writer.writerow(['city', 'country', 'population'])
    writer.writerow(['São Paulo', 'Brasil', 12325232])
    writer.writerow(['München', 'Deutschland', 1512491])
    writer.writerow(['北京', '中国', 21893095])
Pro Tip

Use encoding='utf-8-sig' whenever your CSV will be opened in Excel. Plain utf-8 is correct for data pipelines and server-side code. If you are unsure, utf-8-sig is the safer default for files shared with humans.

Writing CSV to a String with io.StringIO

There are situations where writing a CSV file to disk is the wrong approach: generating a CSV payload to attach to an email, returning CSV as an HTTP response in a web framework, or building a test fixture in memory. For these cases, Python's io.StringIO acts as an in-memory file object that the csv module can write to exactly like a real file. You then retrieve the full CSV content as a string with getvalue().

python
import csv
import io

def build_csv_string(rows: list[dict], fields: list[str]) -> str:
    """Return a CSV-formatted string without writing any file."""
    output = io.StringIO()
    writer = csv.DictWriter(output, fieldnames=fields)
    writer.writeheader()
    writer.writerows(rows)
    return output.getvalue()


employees = [
    {'name': 'Alice', 'department': 'Engineering', 'salary': 95000},
    {'name': 'Bob',   'department': 'Marketing',   'salary': 72000},
]

csv_text = build_csv_string(employees, fields=['name', 'department', 'salary'])
print(csv_text)
# name,department,salary
# Alice,Engineering,95000
# Bob,Marketing,72000

Handling Extra Fields with extrasaction

csv.DictWriter raises a ValueError by default if any dictionary in your data contains a key that is not listed in fieldnames. This strict behaviour is intentional — it prevents silent data loss — but there are legitimate cases where you want to write only a subset of fields from a wider dictionary. Setting extrasaction='ignore' tells the writer to silently discard keys outside fieldnames rather than raising an error.

python
import csv

# Each record has more keys than we want in the output file
records = [
    {'id': 1, 'name': 'Alice', 'email': 'alice@example.com',
     'internal_notes': 'CONFIDENTIAL', 'salary': 95000},
    {'id': 2, 'name': 'Bob',   'email': 'bob@example.com',
     'internal_notes': 'CONFIDENTIAL', 'salary': 72000},
]

# Only id, name, and email go into the public export
export_fields = ['id', 'name', 'email']

with open('public_export.csv', 'w', newline='') as file:
    writer = csv.DictWriter(
        file,
        fieldnames=export_fields,
        extrasaction='ignore',   # silently drops internal_notes and salary
    )
    writer.writeheader()
    writer.writerows(records)
Use extrasaction='ignore' with care

The default extrasaction='raise' exists to catch bugs. Only switch to 'ignore' when you are intentionally writing a projection of a wider record and you know which fields you are dropping. If extra keys appear unexpectedly and you ignore them, you will not find out until data is missing from your output file.

Writing a CSV with a lineterminator Override

The csv module defaults to \r\n as the line terminator (following RFC 4180), which is why newline='' is required on open() — without it, Windows doubles up to \r\r\n. On Unix systems where you know the file will only ever be read by Unix tools, you can explicitly set lineterminator='\n' to produce Unix-style line endings:

python
import csv

# Unix line endings — only use when the file stays on Unix systems
with open('unix_report.csv', 'w', newline='') as file:
    writer = csv.writer(file, lineterminator='\n')
    writer.writerow(['host', 'status', 'response_ms'])
    writer.writerow(['web-01', 'up', 42])
    writer.writerow(['web-02', 'up', 38])
    writer.writerow(['db-01',  'up', 11])
How-To Guide
Write to a CSV File in Python
  1. Import the csv module

    At the top of your script, add import csv. The module is part of Python's standard library — no installation is needed.

  2. Open a file in write mode

    Use with open('yourfile.csv', 'w', newline='') as file: to open the file safely. The newline='' argument prevents extra blank lines. The with statement ensures the file closes automatically.

  3. Create a writer object

    Pass the open file to csv.writer(file) or csv.DictWriter(file, fieldnames=fields) depending on whether your data is in lists or dictionaries.

  4. Write the header row

    With csv.writer, call writer.writerow(['col1', 'col2', ...]). With csv.DictWriter, call writer.writeheader() to write the column names automatically.

  5. Write your data rows

    Call writer.writerow(row) for each individual row, or writer.writerows(all_rows) to write a collection of rows in one call. The file closes automatically when the with block ends.

Key Points to Remember

01

Always use newline='' when opening a CSV file for writing. This prevents extra blank lines between rows on Windows.

02

Use with open(...) as file: to open files. The with statement closes the file automatically, even if an error occurs.

03

csv.writer works with lists. csv.DictWriter works with dictionaries and adds the convenience of writeheader().

04

Use 'w' mode to create or overwrite a file. Use 'a' mode to append new rows to an existing file without deleting its content.

05

writerow() writes a single row. writerows() writes multiple rows from a list of rows in one call.

06

The csv module automatically quotes values that contain commas, so you do not need to handle that case manually.

07

Use encoding='utf-8-sig' when writing CSV files that will be opened in Excel. The BOM it adds tells Excel to interpret the file as UTF-8.

08

Write to io.StringIO instead of a file when you need a CSV string in memory — for HTTP responses, email attachments, or test fixtures.

09

Use extrasaction='ignore' on DictWriter when your dictionaries contain more keys than you want to write. Leave the default 'raise' when extra keys should be an error.

10

Control quoting with the quoting parameter. csv.QUOTE_ALL quotes every field; csv.QUOTE_NONNUMERIC quotes only strings; csv.QUOTE_NONE never quotes and requires an escapechar.

Key Concepts — Click to Explore
  • csv module
  • csv.writer
  • csv.DictWriter
  • writerow()
  • writerows()
  • writeheader()
  • newline=''
  • with statement
  • append mode
  • fieldnames
  • dialect
  • quoting parameter
  • utf-8-sig
  • io.StringIO
  • extrasaction

Frequently Asked Questions

Python's built-in csv module handles CSV writing. You import it with import csv and then use csv.writer() or csv.DictWriter() to write rows to a file. No third-party packages are needed.

This happens because Python applies its own newline handling on top of what the csv module produces. Fix it by opening the file with newline='': open('file.csv', 'w', newline=''). This disables Python's translation and lets the csv module manage line endings itself.

csv.writer writes rows as plain lists, so you pass a list of values to writerow(). csv.DictWriter writes rows as dictionaries, mapping column names to values, which makes the code easier to read when your data already has named fields. DictWriter also provides a dedicated writeheader() method.

Open the file in append mode by passing 'a' as the mode argument: open('file.csv', 'a', newline=''). This adds new rows after the existing content. Do not call writeheader() when appending, as the header is already in the file.

With csv.writer, call writer.writerow() before your data rows and pass the column names as a list. With csv.DictWriter, call writer.writeheader() to write the fieldnames automatically.

Yes. Use csv.DictWriter, pass your column names as fieldnames, call writeheader(), then loop through your list of dictionaries and call writer.writerow(row) for each one, or pass the entire list to writer.writerows().

No. The csv module is part of Python's standard library and is available in every Python installation. You only need import csv at the top of your script.

It tells Python not to apply universal newline translation when opening the file. This lets the csv module control line endings itself, which prevents the extra blank lines that would otherwise appear between rows on Windows.

Excel does not automatically detect UTF-8 encoding unless the file contains a byte order mark (BOM). Open your file with encoding='utf-8-sig' instead of plain encoding='utf-8'. The utf-8-sig codec writes a BOM at the start of the file that signals to Excel how to interpret the character encoding.

Use io.StringIO as the target instead of a real file. Create the writer with csv.writer(output) or csv.DictWriter(output, fieldnames=fields) where output = io.StringIO(), write your rows as normal, then call output.getvalue() to retrieve the complete CSV content as a string. This is the standard approach for HTTP responses and email attachments.

Pass extrasaction='ignore' to csv.DictWriter. By default, the writer raises a ValueError if any dictionary contains keys not in fieldnames. Setting extrasaction='ignore' tells it to silently drop those extra keys so only the fields you declared are written to the file.

Final Exam

Answer all 10 questions to earn your certificate of completion. A score of 80% or higher is required.

Final Exam
Write to a CSV File in Python

Enter your name as it should appear on the certificate, then start the exam.