Question
Remove a Key from a Hash in Ruby: delete vs reject and Returning the Remaining Hash
Question
In Ruby or Rails, how can you remove a key from a Hash and get back the remaining hash?
For example, adding a new key-value pair works like this:
{ a: 1, b: 2 }.merge!(c: 3)
# => { a: 1, b: 2, c: 3 }
Is there a similar built-in way to delete a key from a hash?
This works:
{ a: 1, b: 2 }.reject! { |k, _v| k == :a }
# => { b: 2 }
But I would expect something like:
{ a: 1, b: 2 }.delete!(:a)
# => { b: 2 }
The important part is that the return value should be the remaining hash, so it can be used inline, for example:
foo(my_hash.reject! { |k, _v| k == my_key })
Short Answer
By the end of this page, you will understand how Ruby Hash deletion methods work, especially the difference between removing a key and what value gets returned. You will learn when to use delete, reject, and non-mutating alternatives, how Rails adds helpers like except, and how to safely write one-line expressions that return the updated hash.
Concept
Ruby Hash objects have methods for both changing the original hash and creating a new hash.
The main idea behind this question is that different methods return different things:
merge!modifies the hash and returns the updated hash.deletemodifies the hash but returns the deleted value, not the remaining hash.rejectreturns a new hash with filtered entries removed.reject!modifies the original hash, but its return value has a special rule.
This matters because in real code, developers often want to chain methods or pass results directly into another method. To do that safely, you need to know exactly what each method returns.
In plain Ruby:
h = { a: 1, b: 2 }
h.delete(:a)
# => 1
The hash becomes:
{ b: 2 }
But the returned value is 1, because delete is designed to return the value that was removed.
If your goal is to get the , you usually want one of these patterns:
Mental Model
Think of a hash like a labeled storage box.
merge!means: put a new labeled item into the box and hand me back the box.deletemeans: remove the item with this label and hand me the item that was removed.rejectmeans: build me a new box with only the items I want to keep.
So even though delete changes the box, its return value is not the box. It is the removed item.
That is why this can feel surprising at first:
h = { a: 1, b: 2 }
removed = h.delete(:a)
# removed is 1, not { b: 2 }
If you need the updated box itself, use a method that returns the box, or return h after deleting.
Syntax and Examples
Core syntax
delete
Removes a key from the original hash and returns the removed value.
h = { a: 1, b: 2 }
result = h.delete(:a)
puts result
# 1
puts h
# {:b=>2}
Return the hash after deleting
If you want the remaining hash, do this:
h = { a: 1, b: 2 }
h.delete(:a)
h
# => { a: 1, b: 2 }?
That would only work if shown after the mutation is complete in an interactive sequence. In a single expression, use tap:
h = { a: 1, b: 2 }
result = h.tap { |hash| hash.delete(:a) }
# => { b: 2 }
reject
Returns a new hash without changing the original.
Step by Step Execution
Consider this example:
h = { a: 1, b: 2 }
result = h.tap { |hash| hash.delete(:a) }
Step by step:
- Ruby creates the hash:
h = { a: 1, b: 2 }
-
tapyieldshinto the block ashash. -
Inside the block:
hash.delete(:a)
This removes the :a key from the original hash.
-
delete(:a)returns1, the deleted value. -
But
tapignores the block's return value and returns the original object instead. -
Since the original object was mutated, the final returned value is:
Real World Use Cases
Removing unwanted keys before sending data
params.except(:password, :token)
Useful for API responses, logs, and serialization.
Cleaning configuration hashes
options = { timeout: 10, debug: true, internal: true }
public_options = options.reject { |k, _| k == :internal }
Useful when passing only allowed options into a library.
Filtering request parameters
In Rails controllers, developers often remove keys they do not want to process further.
safe_params = params.to_h.except("admin")
Mutating data in place for performance or clarity
If you already have a hash and want to update it directly:
session_data.delete(:temporary_token)
This is common when cleaning up state after use.
Building sanitized objects
user_data = { name: "Sam", , }
user_data_without_password = user_data.except()
Real Codebase Usage
In real projects, developers usually choose between mutating and non-mutating approaches based on intent.
1. Non-mutating for safer code
When you do not want side effects, return a new hash.
filtered = original.except(:secret)
This is easier to reason about, especially in larger codebases.
2. Mutating when the original hash should change
payload.delete(:debug)
This is common when a hash is temporary and owned by the current method.
3. Using tap for inline mutation + return object
foo(my_hash.tap { |h| h.delete(my_key) })
This is a common Ruby idiom when you want to mutate and still return the object.
4. Guarding against missing keys
if config.key?(:cache)
config.delete(:cache)
end
Not always necessary, but useful if your logic depends on whether the key existed.
5. Filtering multiple keys
In Rails:
attrs = params.to_h.except(, )
Common Mistakes
Mistake 1: Expecting delete to return the updated hash
Broken expectation:
h = { a: 1, b: 2 }
result = h.delete(:a)
puts result
# expected: {:b=>2}
# actual: 1
How to avoid it:
- Use
deletewhen you need the removed value. - Use
tap,reject, orexceptwhen you need the remaining hash.
Mistake 2: Using reject! inline without handling nil
h = { a: 1, b: 2 }
foo(h.reject! { |k, _| k == :z })
This may pass nil to foo because no key matched.
How to avoid it:
Comparisons
| Method | Mutates original hash? | Return value | Best use case |
|---|---|---|---|
delete(:key) | Yes | Deleted value | When you need the removed value |
reject { ... } | No | New filtered hash | When you want a new hash |
reject! { ... } | Yes | Modified hash or nil | When mutating is fine and nil is acceptable |
except(:key) (Rails) | No | New hash without key | Cleanest Rails option for removing keys |
| `tap { |
Cheat Sheet
# Remove one key, mutate original, return deleted value
h.delete(:a)
# Remove one key, mutate original, return mutated hash
h.tap { |hash| hash.delete(:a) }
# Remove by condition, return new hash
h.reject { |k, v| k == :a }
# Remove by condition, mutate original
# returns mutated hash OR nil if nothing changed
h.reject! { |k, v| k == :a }
# Rails: return new hash without given keys
h.except(:a)
h.except(:a, :b)
Rules to remember
deletereturns the deleted value.rejectreturns a new hash.reject!may return nil.exceptis a Rails-friendly way to remove keys without mutation.- Use
tapif you want to mutate and still return the hash.
Safe inline patterns
foo(my_hash.reject { |k, _| k == my_key })
foo(my_hash.except(my_key)) # Rails
foo(my_hash.tap { |h| h.delete(my_key) })
FAQ
Is there a delete! method for Ruby hashes?
No. Ruby Hash has delete, but not delete!.
Why does Hash#delete return the deleted value instead of the hash?
That is how the method is designed. It is useful when you need the removed value immediately.
How do I remove a key and return the remaining hash in one line?
Use one of these:
h.reject { |k, _| k == :a }
h.except(:a) # Rails
h.tap { |hash| hash.delete(:a) }
What is the safest choice for method chaining?
Usually reject or Rails except, because they return a hash consistently.
Why can reject! return nil?
Because Ruby uses nil to indicate that no change was made.
Should I use delete or for one key?
Mini Project
Description
Build a small Ruby utility that sanitizes user data before sending it to another part of your application. This demonstrates how to remove keys from a hash safely, how mutation affects the original object, and when returning a new hash is the better choice.
Goal
Create a method that removes sensitive keys from a hash and returns the cleaned hash.
Requirements
- Create a hash containing user data such as name, email, password, and token.
- Write one method that returns a new sanitized hash without modifying the original.
- Write another method that removes a key in place and returns the mutated hash.
- Show the difference in output between the original hash and the returned result.
- Use at least one example that removes more than one key.
Keep learning
Related questions
How to Call Shell Commands from Ruby and Capture Output
Learn how to run shell commands in Ruby, capture output, check exit status, and choose the right method for scripts and apps.
How to Check Whether a String Contains a Substring in Ruby
Learn how to check if a string contains a substring in Ruby using include?, match, and multiline string examples.
How to Check if a Hash Key Exists in Ruby
Learn how to check whether a specific key exists in a Ruby hash using key?, has_key?, and include? with clear examples.