iOS
UITableView
Swift
Objective-C
iOS Development

When to use dequeueReusableCellWithIdentifier vs dequeueReusableCellWithIdentifier forIndexPath

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

The two table-view dequeue APIs look similar, but their guarantees are different and that affects crash risk and code complexity. Modern UIKit code should almost always use the for variant. Understanding why helps when maintaining older code or migrating mixed Swift and Objective-C screens.

API Semantics and Return Guarantees

dequeueReusableCell(withIdentifier:) may return nil when no reusable cell is available and no registration exists. dequeueReusableCell(withIdentifier:for:) never returns nil, but it requires a registered class, nib, or prototype cell.

That guarantee is useful because cellForRowAt can focus on data binding instead of fallback creation logic.

swift
1import UIKit
2
3final class ProductsController: UITableViewController {
4    private let cellId = "ProductCell"
5    private let products = ["Keyboard", "Mouse", "Monitor", "Cable"]
6
7    override func viewDidLoad() {
8        super.viewDidLoad()
9        tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellId)
10    }
11
12    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
13        products.count
14    }
15
16    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
17        let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath)
18        cell.textLabel?.text = products[indexPath.row]
19        cell.accessoryType = .disclosureIndicator
20        return cell
21    }
22}

Legacy Optional Dequeue Pattern

The older method still appears in pre-iOS 6 style code and some Objective-C codebases.

swift
1import UIKit
2
3final class LegacyProductsController: UITableViewController {
4    private let cellId = "LegacyProductCell"
5
6    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
7        var cell = tableView.dequeueReusableCell(withIdentifier: cellId)
8        if cell == nil {
9            cell = UITableViewCell(style: .subtitle, reuseIdentifier: cellId)
10        }
11
12        cell?.textLabel?.text = "Row \(indexPath.row)"
13        cell?.detailTextLabel?.text = "Optional dequeue path"
14        return cell!
15    }
16}

This works, but it increases boilerplate and makes consistency harder across teams.

Registration Patterns for Modern Apps

You can register classes, nibs, or use storyboard prototype cells. The important part is one source of truth for the reuse id and early registration.

swift
1import UIKit
2
3final class UserCell: UITableViewCell {
4    @IBOutlet weak var nameLabel: UILabel!
5}
6
7final class UsersController: UITableViewController {
8    private let userCellId = "UserCell"
9
10    override func viewDidLoad() {
11        super.viewDidLoad()
12        let nib = UINib(nibName: "UserCell", bundle: nil)
13        tableView.register(nib, forCellReuseIdentifier: userCellId)
14    }
15
16    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
17        let cell = tableView.dequeueReusableCell(withIdentifier: userCellId, for: indexPath) as! UserCell
18        cell.nameLabel.text = "User \(indexPath.row)"
19        return cell
20    }
21}

When registration happens in one place, crashes from missing identifiers are easier to prevent.

Migration Guidance

For legacy screens, migrate incrementally. First register each cell type explicitly, then switch dequeue calls to the for method, then remove nil fallback logic. Finally, run scrolling and reuse-state tests to confirm behavior.

This staged approach is safer than rewriting all table views at once, especially in apps with many custom cell subclasses.

Performance and State Hygiene

Reusable cells improve scrolling only when state is reset consistently. Set every visual property during binding, including text, image placeholders, accessory type, and hidden flags. If asynchronous image loading is used, cancel stale requests in prepareForReuse to prevent incorrect images from flashing. Keep expensive transformations outside cellForRowAt and precompute view models where possible. During profiling, watch frame drops while fast-scrolling long lists and inspect allocations for unexpected cell creation spikes. Strong reuse discipline matters as much as API choice when you want smooth table performance on older devices.

Common Pitfalls

The most frequent crash is using the for method without registration. Verify registration and reuse id spelling before loading data.

Another pitfall is stale UI state from reuse. If one row hides a badge or sets an image, reset those fields for every row.

Some implementations do expensive formatting in cellForRowAt, causing scroll stutter. Move heavy work to model preparation or background preprocessing.

Finally, avoid force casts unless you fully control registration. Mismatched class and identifier pairs can trigger runtime failures.

Summary

  • Use dequeueReusableCell(withIdentifier:for:) as the default in modern UIKit.
  • Register classes or nibs before first dequeue.
  • Keep reuse ids centralized to prevent typo bugs.
  • Reset all mutable cell state on every bind.
  • Migrate legacy optional dequeue paths incrementally.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.