Interview Question
"When would you avoid Active Record callbacks?"
The Rails interview question that filters for design judgment. Callbacks are a common source of production pain. What the interviewer is checking, when callbacks fit vs do not, the alternatives, and the failure modes that teach the lesson.
What the interviewer is actually checking
Active Record callbacks (after_save, before_destroy, after_commit, and so on) are one of the patterns Rails developers use constantly and rarely question. The interview question filters for whether the candidate has hit the failure modes that come from over-using them and can name when they should be replaced with a different pattern.
A weak answer says "I always use callbacks." A senior answer says "callbacks are right when the side effect is intrinsic to the record's lifecycle and the team can predict it; they are wrong when the side effect is a business operation that happens to be triggered by a record change."
Behind the question is also a check on whether the candidate knows the difference between after_save and after_commit and why that difference matters for external side effects.
The mid-level answer
"Callbacks let you run code when a record is saved or updated. I use them for things like sending notifications after a user signs up, or normalising data before save. They are useful but can get out of control if you stack too many on one model."
Mostly correct, but missing the specifics. The senior answer names the failure modes (callbacks firing inside transactions, callbacks that depend on each other, callbacks that are hard to test, callbacks that run in surprising contexts) and gives a heuristic for when to extract.
The senior answer
Four failure modes seniors avoid by reaching for different patterns.
1. External side effects inside the transaction. "An after_save that calls Stripe or sends an email runs inside the save's transaction. If the transaction rolls back later (a downstream validation fails, an after_save on another callback raises), the email was already sent and the customer was charged. The fix is to use after_commit for external side effects, which runs after the transaction commits, so a rolled-back save never triggers the external call."
2. Callbacks that fire in surprising contexts. "A model has after_create :send_welcome_email. A team member writes a rake task that bulk-creates 50,000 users for a backfill, and sends 50,000 welcome emails by accident. The fix: User.create in production triggers the callback; in a backfill, it should not. Either guard the callback (unless: :skip_welcome_email?), or move the trigger to the controller action that creates real users."
3. Callbacks that make tests slow or brittle. "A model with five after_save callbacks that each touch external services becomes painful to test. Every test fixture creation triggers all five. Tests slow down; mocking gets verbose. The smell: factory_bot factories that have to stub three things only to create a record. The fix: pull the side effects out of the model into service objects or background jobs, fire them from the controller, leave the model with only validations and intrinsic state changes."
4. Callbacks that depend on each other. "When after_save :update_search_index depends on after_save :sync_to_warehouse having run first, and the order silently depends on declaration order in the class file, you have a coupling problem that will bite when someone reorders the file or adds a new callback in the middle. The fix is to extract the orchestration into an explicit class that calls the steps in order, with named methods."
When callbacks ARE the right choice
"Two patterns where callbacks fit cleanly."
Data normalisation. "A before_save :normalize_email that downcases and strips whitespace is intrinsic to the User record's contract. It runs on every save, has no external dependencies, is trivial to test, and the team expects it. Same shape for before_validation :set_default_attributes."
Computed attributes. "A before_save :compute_slug that derives the slug from the title is the right shape. The slug always reflects the title. The callback ensures the invariant holds whether the record was created in a controller, a rake task, or a factory."
"Both patterns share the same property: the callback is intrinsic to the record's data model, deterministic, and has no external side effect. Cross those lines and the callback becomes a maintenance burden."
The alternatives
"Three patterns to reach for when a callback would be wrong."
Service objects. "SignUpUser.call(params) creates the user, then explicitly fires SendWelcomeEmail.call(user) and CreateDefaultWorkspace.call(user). The orchestration is visible in one place; bulk-creating users without those side effects is a plain User.create!. Detailed in Tell me about service objects."
Background jobs called explicitly. "From the controller, after the create succeeds, call WelcomeEmailJob.perform_later(user.id). The job runs in the background. The job is not coupled to the model lifecycle; bulk imports do not trigger it."
Pub/sub or domain events. "For apps with many cross-cutting concerns, an event-bus pattern (Wisper, ActiveSupport::Notifications, or a custom one) lets the model publish 'user.created' and subscribers (in their own files) react. More setup; right for larger apps where the orchestration is genuinely complex."
The follow-ups
"What is the difference between after_save and after_commit?" The right answer: after_save runs inside the database transaction; after_commit runs after the transaction commits. For external side effects (HTTP calls, queueing jobs, sending emails), always after_commit. The transaction can roll back; the email cannot be unsent.
"What about around_save?" Niche. Lets you wrap the save in arbitrary code (typically for instrumentation: timing, transaction tracing). Rarely the right tool for business logic.
"How do you skip callbacks?" The right answer names the tools and the trade-off. update_columns bypasses callbacks and validations. save(validate: false) skips validations but runs callbacks. There is no clean "skip callbacks" without monkey-patching; the senior answer is that if you frequently need to skip them, the callbacks should not have been there to begin with.
"Have you ever written a 200-line model with twenty callbacks?" The honest answer is "yes, on a previous team," followed by the lesson. Most senior Rails developers have lived through this and learned why service objects exist.
The principle at play
Active Record callbacks couple side effects to the persistence lifecycle. That coupling is correct when the side effect IS part of the persistence lifecycle (slug computation, normalisation). It is wrong when the side effect is a business operation that happens to be triggered by a record change (sending an email, charging a card, syncing to an external system).
The senior reflex: when adding a callback, ask "is this intrinsic to the data, or is this a business operation?" The honest answer determines whether it belongs in the model or in a service. Most production Rails pain from callbacks traces to teams not asking this question often enough.