You have a Pandas DataFrame containing employee records. You want to select the row labelled E102, but instead, you accidentally select the second row.

The code runs without any obvious warning, yet the result is wrong.

This usually happens when someone understands how to create a DataFrame but is still confused about the difference between loc[] and iloc[].

Both are used to select rows and columns from a Pandas DataFrame. The main difference is simple:

  • loc[] selects data using row and column labels.
  • iloc[] selects data using numerical positions.

This one distinction controls how Pandas interprets everything written inside the brackets.

According to the official Pandas documentation, loc[] is primarily label-based, while iloc[] is based on integer positions from 0 to length - 1.

Aspiring for a career in Data and Business Analytics? Begin your journey with a Data and Business Analytics Certificate from Jobaaj Learnings.

What Is a Pandas DataFrame?

A Pandas DataFrame is a two-dimensional data structure that stores information in rows and columns.

It looks similar to an Excel worksheet or a table in a database.

Consider the following example:

import pandas as pd

data = {
    "Name": ["Aman", "Priya", "Rahul", "Sneha"],
    "Department": ["Sales", "Finance", "IT", "Marketing"],
    "Salary": [45000, 52000, 60000, 48000]
}

df = pd.DataFrame(
    data,
    index=["E101", "E102", "E103", "E104"]
)

print(df)

Output:

       Name Department  Salary
E101   Aman      Sales   45000
E102  Priya    Finance   52000
E103  Rahul         IT   60000
E104  Sneha  Marketing   48000

This DataFrame contains two different ways to identify a row.

The first row has:

  • Label: E101
  • Position: 0

The second row has:

  • Label: E102
  • Position: 1

This difference between a label and a position is the foundation of loc[] and iloc[].

What Is loc[] in Pandas?

loc[] is used to select rows and columns by their labels or names.

The name can be understood as label location.

Its basic syntax is:

df.loc[row_label, column_label]

Pandas officially defines loc[] as an accessor for selecting groups of rows and columns by labels or Boolean arrays.

Selecting One Row With loc[]

To select the employee with the row label E102, write:

df.loc["E102"]

Output:

Name              Priya
Department      Finance
Salary           52000
Name: E102, dtype: object

Pandas searches for the exact label E102.

It does not count rows from the top.

Selecting One Row and One Column With loc[]

To retrieve Priya’s salary:

df.loc["E102", "Salary"]

Output:

52000

Here:

  • "E102" is the row label.
  • "Salary" is the column label.

Selecting Multiple Rows With loc[]

Pass a list of row labels:

df.loc[["E101", "E103"]]

Output:

       Name Department  Salary
E101   Aman      Sales   45000
E103  Rahul         IT   60000

Notice the double square brackets.

The outer brackets belong to loc[], while the inner brackets contain the list of labels.

Selecting Multiple Rows and Columns With loc[]

df.loc[
    ["E101", "E103"],
    ["Name", "Salary"]
]

Output:

       Name  Salary
E101   Aman   45000
E103  Rahul   60000

This selects two named rows and two named columns.

What Is iloc[] in Pandas?

iloc[] selects rows and columns according to their integer positions.

The letter i in iloc can be remembered as integer location.

Its basic syntax is:

df.iloc[row_position, column_position]

Pandas defines iloc[] as a purely integer-location-based indexer. Positions begin at 0 and continue up to length - 1.

Selecting One Row With iloc[]

To select the second row:

df.iloc[1]

Output:

Name              Priya
Department      Finance
Salary           52000
Name: E102, dtype: object

Python uses zero-based indexing, so:

  • Position 0 means the first row.
  • Position 1 means the second row.
  • Position 2 means the third row.

Selecting One Row and One Column With iloc[]

To retrieve the value from the second row and third column:

df.iloc[1, 2]

Output:

52000

Here:

  • 1 means the second row.
  • 2 means the third column.

Selecting Multiple Rows With iloc[]

df.iloc[[0, 2]]

Output:

       Name Department  Salary
E101   Aman      Sales   45000
E103  Rahul         IT   60000

The row labels remain visible in the result, but they were not used for selection.

Pandas selected the first and third rows based on their positions.

Selecting Multiple Rows and Columns With iloc[]

df.iloc[
    [0, 2],
    [0, 2]
]

Output:

       Name  Salary
E101   Aman   45000
E103  Rahul   60000

The first list selects row positions. The second selects column positions.

loc vs iloc: Main Differences

Feature

loc[]

iloc[]

Selection method

Labels

Integer positions

Full meaning

Label location

Integer location

Row example

df.loc["E102"]

df.iloc[1]

Column example

"Salary"

2

Starting point

Depends on index labels

Always position 0

Slice ending

Included

Excluded

Boolean filtering

Supports aligned Boolean Series

Primarily supports Boolean arrays

Missing value error

Usually KeyError

Usually IndexError

Negative positions

Not normally positional

Supported

Best use

Named rows, columns and conditions

Data based on order or position

The easiest memory trick is:

loc  = labels
iloc = integer positions

The Most Important Difference: Labels vs Positions

Consider a DataFrame with numerical row labels:

data = {
    "Name": ["Aman", "Priya", "Rahul"],
    "Marks": [75, 82, 91]
}

students = pd.DataFrame(
    data,
    index=[10, 20, 30]
)

print(students)

Output:

     Name  Marks
10   Aman     75
20  Priya     82
30  Rahul     91

Now compare the following statements:

students.loc[20]

This selects the row whose label is 20.

students.iloc[1]

This selects the row at position 1, which is the second row.

Both return Priya’s record, but they reach it differently.

Now consider:

students.loc[1]

This raises a KeyError because no row is labelled 1.

But:

students.iloc[1]

works because a second row exists at position 1.

The official Pandas documentation specifically notes that an integer passed to loc[] is treated as a label, never automatically as a row position.

Slicing With loc[] and iloc[]

Slicing is one of the areas where beginners make the most mistakes.

The end point behaves differently in loc[] and iloc[].

Slicing With loc[] Is Inclusive

Consider:

df.loc["E101":"E103"]

Output:

       Name Department  Salary
E101   Aman      Sales   45000
E102  Priya    Finance   52000
E103  Rahul         IT   60000

Both E101 and E103 are included.

Unlike standard Python slicing, label-based slices in loc[] include both the start and stop labels when those labels exist.

Slicing With iloc[] Excludes the End Position

df.iloc[0:3]

Output:

       Name Department  Salary
E101   Aman      Sales   45000
E102  Priya    Finance   52000
E103  Rahul         IT   60000

Positions 0, 1 and 2 are selected.

Position 3 is excluded.

This follows normal Python slicing rules:

start included
stop excluded

Direct Slicing Comparison

df.loc["E101":"E103"]

Includes:

E101, E102 and E103
df.iloc[0:3]

Includes:

positions 0, 1 and 2

Both produce the same rows in this example, but their slicing rules are different.

Selecting Columns With loc[]

To select all rows and one named column:

df.loc[:, "Name"]

Output:

E101     Aman
E102    Priya
E103    Rahul
E104    Sneha
Name: Name, dtype: object

The colon means all rows.

To select multiple columns:

df.loc[:, ["Name", "Salary"]]

Output:

       Name  Salary
E101   Aman   45000
E102  Priya   52000
E103  Rahul   60000
E104  Sneha   48000

To select a range of named columns:

df.loc[:, "Name":"Salary"]

The ending column is included because loc[] uses inclusive label slicing.

Selecting Columns With iloc[]

To select all rows and the first column:

df.iloc[:, 0]

To select the first and third columns:

df.iloc[:, [0, 2]]

Output:

       Name  Salary
E101   Aman   45000
E102  Priya   52000
E103  Rahul   60000
E104  Sneha   48000

To select the first two columns:

df.iloc[:, 0:2]

The column at position 2 is excluded.

Filtering a DataFrame With loc[]

One of the most common uses of loc[] is conditional filtering.

Suppose you want employees earning more than ₹50,000:

df.loc[df["Salary"] > 50000]

Output:

       Name Department  Salary
E102  Priya    Finance   52000
E103  Rahul         IT   60000

The expression:

df["Salary"] > 50000

produces a Boolean Series:

E101    False
E102     True
E103     True
E104    False

loc[] keeps the rows where the condition is True.

Filtering Rows and Selecting Columns Together

df.loc[
    df["Salary"] > 50000,
    ["Name", "Salary"]
]

Output:

       Name  Salary
E102  Priya   52000
E103  Rahul   60000

This is one of the cleanest ways to filter rows while returning only the required columns.

Using Multiple Conditions With loc[]

To select Finance or IT employees earning at least ₹50,000:

df.loc[
    (df["Salary"] >= 50000)
    & (df["Department"].isin(["Finance", "IT"]))
]

Each condition should be placed inside parentheses.

Use:

  • & for AND
  • | for OR
  • ~ for NOT

The Pandas documentation also shows Boolean conditions as a standard use of loc[].

Can iloc[] Be Used for Boolean Filtering?

Yes, but there is an important limitation.

iloc[] works with a Boolean array whose length matches the selected axis.

For example:

mask = [False, True, True, False]

df.iloc[mask]

This selects the second and third rows.

However, directly passing an indexed Boolean Series to iloc[] may raise an error because iloc[] works positionally and does not perform the same label alignment as loc[].

Use a NumPy array when positional Boolean filtering is required:

mask = (df["Salary"] > 50000).to_numpy()

df.iloc[mask]

The official indexing guide explains that loc[] supports aligned Boolean Series, while iloc[] expects a Boolean array rather than an indexed Boolean Series.

For everyday conditional filtering, loc[] is usually clearer.

Selecting the First and Last Rows With iloc[]

Because iloc[] supports positional indexing, it is convenient for selecting rows from the beginning or end.

First Row

df.iloc[0]

Last Row

df.iloc[-1]

Last Two Rows

df.iloc[-2:]

First Three Rows

df.iloc[:3]

Negative positions work in the same way as ordinary Python sequences.

This makes iloc[] useful when the order matters but row labels do not.

Selecting Alternate Rows and Columns

You can add a step value while slicing.

Every Second Row

df.iloc[::2]

This selects positions 0, 2, 4 and so on.

Rows in Reverse Order

df.iloc[::-1]

Every Second Column

df.iloc[:, ::2]

These operations are positional, so iloc[] is usually the natural choice.

Updating Values With loc[]

loc[] is not limited to reading data. It can also be used to update values safely.

Suppose you want to increase Rahul’s salary:

df.loc["E103", "Salary"] = 65000

To update several rows based on a condition:

df.loc[df["Salary"] < 50000, "Salary"] = 50000

This changes the salary of every employee currently earning below ₹50,000.

Creating a New Column With loc[]

df.loc[:, "Status"] = "Active"

Updating Values Based on Conditions

df.loc[df["Salary"] >= 55000, "Level"] = "Senior"
df.loc[df["Salary"] < 55000, "Level"] = "Junior"

Using loc[] for conditional updates is usually clearer and safer than chained indexing.

Updating Values With iloc[]

iloc[] can also update values by position.

df.iloc[0, 2] = 47000

This updates the value at:

  • First row
  • Third column

You can update an entire column position:

df.iloc[:, 2] = df.iloc[:, 2] + 2000

This adds ₹2,000 to every value in the third column.

The code works, but label-based updates are often easier to understand when column names are available.

Compare:

df.iloc[:, 2] = df.iloc[:, 2] + 2000

with:

df.loc[:, "Salary"] = df["Salary"] + 2000

The second statement makes the business meaning more obvious.

Why Chained Indexing Should Be Avoided

A beginner may write:

df[df["Salary"] < 50000]["Salary"] = 50000

This is unreliable because it may operate on an intermediate object rather than updating the original DataFrame as intended.

A clearer approach is:

df.loc[df["Salary"] < 50000, "Salary"] = 50000

The row condition and target column are handled in one explicit operation.

For production data work, explicit accessors such as loc[] and iloc[] are preferable to ambiguous indexing patterns. The official Pandas guide recommends using its dedicated data-access methods for more controlled indexing.

Difference in Error Types

loc[] and iloc[] can produce different errors because they search for different things.

loc[] and KeyError

df.loc["E999"]

This raises:

KeyError

The label E999 does not exist.

The Pandas documentation states that loc[] raises a KeyError when requested labels are not found.

iloc[] and IndexError

df.iloc[10]

This raises:

IndexError

The DataFrame does not contain a row at position 10.

Pandas states that iloc[] raises an IndexError for an out-of-bounds position, except when an out-of-range slice is used.

Out-of-Range Slices With iloc[]

This usually does not raise an error:

df.iloc[1:100]

Pandas simply returns all available rows from position 1 onward.

This matches normal Python and NumPy slicing behaviour.

Single Brackets vs Double Brackets

The number of brackets can change the result type.

Selecting a Single Row

df.loc["E101"]

Returns a Series.

df.loc[["E101"]]

Returns a DataFrame.

The same pattern applies to iloc[]:

df.iloc[0]

Returns a Series.

df.iloc[[0]]

Returns a DataFrame.

This matters when the selected data will be passed to another function that expects a DataFrame.

loc[] and iloc[] With Default Indexes

Suppose no custom index is assigned:

df = pd.DataFrame({
    "Name": ["Aman", "Priya", "Rahul"],
    "Marks": [75, 82, 91]
})

The index will be:

0, 1, 2

In this case:

df.loc[1]

and:

df.iloc[1]

return the same row.

However, they still do not mean the same thing.

  • df.loc[1] searches for the label 1.
  • df.iloc[1] selects the second row.

The difference becomes visible after changing or rearranging the index.

Understanding loc[] After Sorting

Consider:

df = pd.DataFrame({
    "Name": ["Aman", "Priya", "Rahul"],
    "Marks": [75, 82, 91]
}, index=[101, 102, 103])

df = df.sort_values("Marks", ascending=False)

The new row order is:

103  Rahul
102  Priya
101  Aman

Now:

df.loc[101]

selects Aman because his row label is 101.

But:

df.iloc[0]

selects Rahul because Rahul currently occupies the first position.

This example shows why loc[] is stable around labels, while iloc[] depends on the present order of the DataFrame.

Using Date Labels With loc[]

loc[] is especially useful for time-series data.

sales = pd.DataFrame(
    {
        "Revenue": [12000, 15000, 17000, 14000]
    },
    index=pd.to_datetime([
        "2026-01-01",
        "2026-01-02",
        "2026-01-03",
        "2026-01-04"
    ])
)

Select one date:

sales.loc["2026-01-02"]

Select a date range:

sales.loc["2026-01-02":"2026-01-04"]

The ending date is included.

Label-based selection makes loc[] a natural option for DataFrames indexed by dates, employee IDs, order IDs or other meaningful identifiers.

Using loc[] With Column Conditions

loc[] can also filter columns using a Boolean condition.

Consider:

scores = pd.DataFrame({
    "Maths": [80, 70, 90],
    "English": [60, 75, 85],
    "Science": [88, 79, 92]
})

To select columns whose average is above 80:

scores.loc[:, scores.mean() > 80]

The row section uses : to select every row.

The column section uses a Boolean condition.

Using iloc[] in Machine Learning Workflows

iloc[] is commonly useful when a dataset follows a fixed positional structure.

Suppose the first four columns are features and the last column is the target:

X = df.iloc[:, 0:4]
y = df.iloc[:, -1]

Here:

  • X contains all rows and the first four columns.
  • y contains all rows and the last column.

This approach is concise, but it depends on column order.

When column names are known, label-based selection may be more readable:

X = df.loc[:, ["Age", "Income", "Experience", "Score"]]
y = df.loc[:, "Purchased"]

The better choice depends on whether column identity or column position is more important.

When Should You Use loc[]?

Use loc[] when:

  • Rows have meaningful labels.
  • You know the column names.
  • You need conditional filtering.
  • You want to update rows based on a condition.
  • You are working with dates or identifiers.
  • Code readability is important.
  • You want label-based slicing.
  • Boolean Series alignment is useful.

Examples:

df.loc["E102"]
df.loc[:, ["Name", "Salary"]]
df.loc[df["Salary"] > 50000]
df.loc[df["Department"] == "IT", "Salary"] = 65000

When Should You Use iloc[]?

Use iloc[] when:

  • You need the first, second or last row.
  • Selection depends on numerical order.
  • Column labels are unknown or unnecessary.
  • You want Python-style positional slicing.
  • You need every second row or column.
  • You are dividing a dataset by column position.
  • You want to select rows after sorting based on their current order.

Examples:

df.iloc[0]
df.iloc[-1]
df.iloc[0:3]
df.iloc[:, [0, 2]]

loc[] vs iloc[] Practical Examples

Select the Third Row

Using position:

df.iloc[2]

Using label:

df.loc["E103"]

Select Name and Salary Columns

Using labels:

df.loc[:, ["Name", "Salary"]]

Using positions:

df.iloc[:, [0, 2]]

Select First Two Rows

Using positions:

df.iloc[0:2]

Using labels:

df.loc["E101":"E102"]

Select Employees With Salary Above ₹50,000

df.loc[df["Salary"] > 50000]

Select the Last Two Rows

df.iloc[-2:]

Retrieve a Single Value

Using labels:

df.loc["E102", "Salary"]

Using positions:

df.iloc[1, 2]

What Are at[] and iat[]?

Pandas also provides at[] and iat[] for accessing one scalar value.

at[] Uses Labels

df.at["E102", "Salary"]

iat[] Uses Positions

df.iat[1, 2]

The relationship is:

Accessor

Selection method

Best for

loc[]

Labels

One or more rows and columns

iloc[]

Positions

One or more rows and columns

at[]

Labels

One scalar value

iat[]

Positions

One scalar value

Pandas recommends at[] or iat[] when only one value needs to be retrieved or changed.

Common Mistakes With loc[] and iloc[]

Mistake 1: Passing Column Names to iloc[]

Incorrect:

df.iloc[:, ["Name", "Salary"]]

iloc[] expects integer positions, not column names.

Correct:

df.iloc[:, [0, 2]]

Or:

df.loc[:, ["Name", "Salary"]]

Mistake 2: Passing Positions to loc[] Without Checking Labels

df.loc[0]

This does not automatically mean the first row.

It searches for a row whose label is 0.

For the first row, use:

df.iloc[0]

Mistake 3: Forgetting the Inclusive loc[] Slice

df.loc["E101":"E103"]

This includes E103.

Students often expect standard Python behaviour and assume the last label will be excluded.

Mistake 4: Forgetting the Exclusive iloc[] Slice

df.iloc[0:3]

This does not include position 3.

It selects positions 0, 1 and 2.

Mistake 5: Mixing Labels and Positions

Incorrect:

df.loc["E102", 2]

This searches for a column labelled 2.

Correct label-based version:

df.loc["E102", "Salary"]

Correct position-based version:

df.iloc[1, 2]

Mistake 6: Using an Indexed Boolean Series With iloc[]

Potentially incorrect:

df.iloc[df["Salary"] > 50000]

Better:

df.loc[df["Salary"] > 50000]

Or convert the condition to an array:

df.iloc[(df["Salary"] > 50000).to_numpy()]

Mistake 7: Relying on Positions After Column Reordering

Suppose salary is originally the third column:

df.iloc[:, 2]

If someone later rearranges the DataFrame, position 2 may represent another column.

Using:

df.loc[:, "Salary"]

is often safer when the column identity matters.

Is loc[] Faster Than iloc[]?

The speed difference is usually not important for normal analysis.

The correct selection method matters more than a small performance difference.

Use:

  • loc[] when your logic depends on labels.
  • iloc[] when your logic depends on positions.
  • at[] or iat[] when repeatedly accessing one scalar value.

Do not choose an accessor only because it appears shorter.

Readable and correct code is more valuable than saving a small amount of execution time in ordinary DataFrame operations.

Why loc[] and iloc[] Matter for Data Careers

Data Analysts, Business Analysts, Data Scientists and Python Developers regularly work with tabular data.

Understanding loc[] and iloc[] helps with:

  • Data cleaning
  • Row filtering
  • Feature selection
  • Updating incorrect values
  • Building reports
  • Preparing machine learning data
  • Analysing time-series records
  • Creating reusable data pipelines

These concepts also appear frequently in Python and Pandas interviews.

An interviewer may provide a DataFrame and ask you to:

  • Select particular rows.
  • Filter employees by salary.
  • Retrieve the last column.
  • Update values conditionally.
  • Explain inclusive and exclusive slicing.
  • Compare loc[], iloc[], at[] and iat[].

Memorising syntax is not enough. You should be able to explain why a particular accessor is appropriate.

Pandas loc and iloc Interview Questions

What Does loc[] Stand For?

It can be remembered as label location.

It selects rows and columns using labels.

What Does iloc[] Stand For?

It means integer location.

It selects rows and columns using zero-based positions.

Does loc[] Include the Ending Label?

Yes. A label slice such as:

df.loc["A":"C"]

includes both A and C when those labels exist.

Does iloc[] Include the Ending Position?

No. A slice such as:

df.iloc[0:3]

includes positions 0, 1 and 2.

Can loc[] Use Boolean Conditions?

Yes.

df.loc[df["Salary"] > 50000]

is a standard conditional-filtering pattern.

Can iloc[] Use Negative Numbers?

Yes.

df.iloc[-1]

selects the last row.

What Error Does loc[] Raise for a Missing Label?

It usually raises a KeyError.

What Error Does iloc[] Raise for an Invalid Position?

It usually raises an IndexError.

Practice Exercise

Create the following DataFrame:

import pandas as pd

data = {
    "Student": ["Ankit", "Meera", "Rohan", "Kavya", "Zoya"],
    "Course": ["Python", "SQL", "Power BI", "Python", "Excel"],
    "Score": [78, 85, 72, 91, 88]
}

students = pd.DataFrame(
    data,
    index=["S01", "S02", "S03", "S04", "S05"]
)

Try completing these tasks:

  1. Select the row labelled S03.
  2. Select the third row by position.
  3. Select the Student and Score columns.
  4. Select the first three rows.
  5. Select students scoring above 80.
  6. Select the final row.
  7. Change the score of S01 to 82.
  8. Select every second row.

Solutions

# 1. Row labelled S03
students.loc["S03"]

# 2. Third row by position
students.iloc[2]

# 3. Student and Score columns
students.loc[:, ["Student", "Score"]]

# 4. First three rows
students.iloc[:3]

# 5. Scores above 80
students.loc[students["Score"] > 80]

# 6. Final row
students.iloc[-1]

# 7. Update S01 score
students.loc["S01", "Score"] = 82

# 8. Every second row
students.iloc[::2]

Quick Summary of loc[] and iloc[]

Use loc[] when you know labels:

df.loc[row_label, column_label]

Use iloc[] when you know positions:

df.iloc[row_position, column_position]

Remember:

loc  = label-based
iloc = integer position-based

Also remember the slicing rule:

loc  includes the ending label
iloc excludes the ending position

Conclusion

The difference between loc[] and iloc[] is not about which one is better.

They solve different selection problems.

loc[] is the better choice when your analysis depends on meaningful names, IDs, dates, conditions or column labels. iloc[] is better when your analysis depends on row order, column order or numerical positions.

Before writing the code, ask one question:

Am I selecting this data because of what it is called, or because of where it appears?

Use loc[] when the answer is what it is called.

Use iloc[] when the answer is where it appears.

Ready to Take the Next Step in Your Career? Apply Now!