Skip to content
Prepare to Grow

iOS Architecture

Feature-Based Project Organization

Organize source code around product capabilities while preserving clear app, feature, domain, and shared boundaries.

Advanced5 min read2 guided activities

Before you begin

What you will understand

  • Compare feature-first and type-first organization.
  • Design a project tree that communicates product boundaries.
  • Keep shared code intentional and minimal.
  • Evolve folders before extracting build modules.
01

Learning stage

Concept Explanation

Type-first organization places every view controller, model, and service in application-wide buckets. It looks tidy in a small project but scatters one product feature across distant directories. Feature-first organization keeps code that changes together close: CourseList can contain its view, presentation model, routes, and feature-specific models.

The application layer owns composition, scene setup, top-level routing, and environment selection. Feature folders own user-visible capabilities. Domain or core areas hold genuinely cross-feature policy, while infrastructure contains concrete system adapters. Shared UI should include stable reusable components, not every view used twice.

Folders communicate intent but do not enforce dependency rules. Begin with namespaces, access control, review conventions, and tests. Extract a Swift package or target only when build isolation, ownership, reuse, dependency enforcement, or build performance makes the extra boundary worthwhile.

  • Feature: cohesive user-facing capability.
  • Application layer: composition and top-level lifecycle.
  • Core/domain: stable cross-feature policy and values.
  • Infrastructure: concrete external-system adapters.
  • Shared UI: deliberate reusable design components.
  • Feature API: smallest surface other features need to access.
02

Learning stage

Syntax & Examples

This tree keeps feature code together while making composition and truly shared capabilities visible.

Project structuretext
LearningApp/
├── App/
│   ├── LearningApp.swift
│   ├── AppDependencies.swift
│   └── AppCoordinator.swift
├── Features/
│   ├── CourseLibrary/
│   │   ├── CourseLibraryView.swift
│   │   ├── CourseLibraryModel.swift
│   │   ├── CourseLibraryRoute.swift
│   │   └── CourseLibraryTests.swift
│   └── LessonPlayer/
│       ├── LessonPlayerViewController.swift
│       ├── LessonPlayerViewModel.swift
│       └── LessonPlayerTests.swift
├── Core/
│   ├── LearningModels/
│   └── ProgressRules/
├── Infrastructure/
│   ├── Networking/
│   └── Persistence/
└── DesignSystem/
    ├── Components/
    └── Tokens/
Product features remain cohesive; app composition and shared policy or infrastructure have explicit homes.
03

Learning stage

Real-World Usage

Connect the architectural concept to maintainable iOS features and real change scenarios.

  • Let a team own a feature without editing broad technical buckets.
  • Delete or replace a feature with a clearly bounded change set.
  • Find routes, presentation state, and tests near the screen they support.
  • Prepare high-value boundaries for later package extraction.
04

Learning stage

Common Mistakes

Identify abstractions and dependencies that add coupling, ambiguity, or unnecessary ceremony.

  • Creating a Shared folder that becomes the largest part of the application.
  • Duplicating domain types in every feature without deciding ownership.
  • Letting features import each other's internal presentation types.
  • Treating folder structure as dependency enforcement.
  • Extracting dozens of tiny modules before stable boundaries exist.
05

Learning stage

Practice Exercises

Apply the design to a small feature and evaluate it through observable change scenarios.

Activity 1

Reorganize a type-first project

Move a sample project's Controllers, Views, ViewModels, and Services into app, feature, core, infrastructure, and design-system areas. Explain each ambiguous placement.

Hint: Place code with the feature that owns its change until cross-feature reuse is proven.

Activity 2

Audit shared code

Review every item in a Shared folder and classify it as stable cross-feature API, feature-owned code, infrastructure, or accidental convenience.

Hint: Two uses are evidence to examine, not automatic proof of a shared abstraction.

06

Learning stage

Knowledge Check

Answer each question before revealing the explanation.

QUESTION 1

What is the main advantage of feature-first organization?

  1. A.Code that changes for one product capability stays close together
  2. B.It removes all dependencies
  3. C.It guarantees faster compilation
  4. D.It requires one file per feature
Reveal answer

Code that changes for one product capability stays close together

Cohesion improves discovery, ownership, and the ability to change or remove a feature.

QUESTION 2

Do folders enforce architectural dependency direction?

Reveal answer

No. They communicate intent, while modules, access control, tests, and tooling provide stronger enforcement.

Any source in one app target can usually reference another internal type regardless of its folder.

07

Learning stage

Interview Preparation

Explain the decision, dependency direction, trade-offs, and evidence—not only the pattern name.

How would you organize a growing iOS codebase?

Short answer: Primarily by product feature, with small explicit app, domain, infrastructure, and design-system areas.

Explore the detailed answer

I keep feature presentation, routes, and tests together, define ownership for shared domain values, and prevent Shared from becoming a dumping ground. I begin with logical boundaries and access control, then extract modules only when they improve build isolation, ownership, reuse, or dependency enforcement enough to justify their cost.

Possible follow-ups

  • • When should two features share a model?
  • • When would you extract a feature into a package?
08

Learning stage

Key Takeaways

Feature-based organization aligns the source tree with product change, while deliberate shared areas and later module boundaries keep coupling visible.

  • Keep code that changes together close.
  • Let the app layer own composition.
  • Share only stable cross-feature capabilities.
  • Folders communicate but do not enforce boundaries.
  • Extract modules when a real organizational or technical benefit exists.