iOS
Navigation Controller
Swift
UIKit
Mobile Development

How to identify previous view controller in navigation stack

Interview Questions practice on Codemia

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

Browse interview questions

Introduction

If a view controller is inside a UINavigationController, the previous screen is usually the element just before the current one in navigationController?.viewControllers. That is the direct mechanical answer.

However, reaching backward into the stack is often a design smell. It works, but in many cases a delegate, closure, or shared state object is a cleaner way to pass data back.

Read the Previous Controller from the Stack

The navigation stack is an array of view controllers. If the current controller is not the root, the previous controller is at index count - 2.

Here is a simple Swift example:

swift
1import UIKit
2
3final class DetailsViewController: UIViewController {
4    func previousViewController() -> UIViewController? {
5        guard let stack = navigationController?.viewControllers,
6              let currentIndex = stack.firstIndex(of: self),
7              currentIndex > 0 else {
8            return nil
9        }
10
11        return stack[currentIndex - 1]
12    }
13}

If you only care about the immediate previous screen and the current controller is at the top of the stack, this shorter form also works:

swift
let stack = navigationController?.viewControllers
let previous = stack?[safe: (stack?.count ?? 0) - 2]

The explicit index lookup is safer because it does not assume the current controller is always the last one at the moment you inspect the stack.

Cast to the Expected Type

In practice, you usually want a specific controller type:

swift
if let previous = previousViewController() as? CheckoutViewController {
    previous.refreshSummary()
}

This is fine when the navigation flow is fixed and the previous screen really should be that type.

A Better Pattern for Passing Data Back

If the goal is to send data back to the previous controller, a delegate or closure is usually better than reaching into the stack and mutating another controller directly.

For example:

swift
1import UIKit
2
3final class EditNameViewController: UIViewController {
4    var onSave: ((String) -> Void)?
5
6    func save(name: String) {
7        onSave?(name)
8        navigationController?.popViewController(animated: true)
9    }
10}

Then the previous screen sets the callback before pushing:

swift
1let edit = EditNameViewController()
2edit.onSave = { [weak self] newName in
3    self?.titleLabel.text = newName
4}
5navigationController?.pushViewController(edit, animated: true)

This avoids coupling the pushed controller to a specific previous controller class.

When Stack Inspection Is Still Useful

Reading the stack directly is still useful when you need to:

  • inspect navigation flow for debugging
  • decide whether a controller was pushed from a specific screen
  • pop back to a known controller type

For example:

swift
if let target = navigationController?.viewControllers.first(where: { $0 is HomeViewController }) {
    navigationController?.popToViewController(target, animated: true)
}

That is often safer than assuming the previous controller is always the one you want.

It is also useful during debugging because you can log the whole stack and confirm whether the user arrived through the path you expected.

That can save time when navigation flows become more complex.

Common Pitfalls

  • Assuming the previous controller is always at count - 2 without checking that the stack is large enough.
  • Reaching into the stack to mutate another controller when a delegate or closure would be cleaner.
  • Force-casting the previous controller to a type that may not actually be there.
  • Treating the navigation stack as a general state-management mechanism.
  • Forgetting that modal presentation is different from a navigation-controller push.

Summary

  • The immediate previous controller in a navigation stack is usually the element before the current controller in viewControllers.
  • You can inspect the stack directly, but type-check and bounds-check it carefully.
  • For returning data, delegates and closures are usually cleaner than stack mutation.
  • Stack inspection is still useful for debugging and targeted pop operations.
  • Choose the approach that matches the navigation problem, not just the easiest array access.

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.