Object#dup
obj.dup -> obj dup creates a shallow copy of an object. It copies the object’s instance variables but not its singleton class (methods defined on that specific object).
Syntax
obj.dup
Parameters
dup accepts no parameters.
A call to dup produces a new object with the same instance variables as the receiver. The two objects share no identity — dup_obj.equal?(original) always returns false — but they start with identical state. Modifying one does not affect the other at the top level, which is the core guarantee that makes defensive copying practical.
Examples
Basic usage
original = "hello"
copy = original.dup
original << " world"
puts original # => "hello world"
puts copy # => "hello"
The duplicated string starts with the same content as the original, but they are separate objects in memory. Appending to the original does not change the duplicate, which is the key reason dup is used for defensive copying in methods that receive mutable arguments.
With freeze
original = "immutable".freeze
copy_dup = original.dup
copy_dup << " modified" # => "immutable modified"
# dup does NOT preserve freeze status
puts copy_dup.frozen? # => false
Freezing an object prevents in-place changes, but dup strips the frozen status from the duplicate. This is useful when you receive a frozen value from a library or config and need to produce a mutable version for further processing.
Duplicating an array
original = [1, 2, [3, 4]]
copy = original.dup
original[2] << 5
puts original.inspect # => [1, 2, [3, 4, 5]]
puts copy.inspect # => [1, 2, [3, 4, 5]] - nested array is shared!
The shared nested array is the defining characteristic of a shallow copy. dup does not recurse into child objects, so any nested collections or mutable values remain shared between the original and the duplicate. When deep copying is needed, you have to handle those inner objects yourself.
Duplicating to prevent mutation
def process_data(data)
# Create a copy to avoid mutating the original
working_copy = data.dup
working_copy.map! { |x| x * 2 }
working_copy
end
numbers = [1, 2, 3]
result = process_data(numbers)
puts result # => [2, 4, 6]
puts numbers # => [1, 2, 3] - original unchanged
Using dup at the top of a method that receives an array, hash, or string prevents accidental mutation of the caller’s data. The duplicated value can be sorted, filtered, or transformed freely, and the original stays intact no matter what the method does internally.
Common Patterns
Defensive copying: Always dup input arguments if your method modifies them.
def sort_and_format(items)
items = items.dup # Don't mutate caller's array
items.map(&:to_s).sort
end
Errors
TypeError— If the object is undupable (some special objects).
when dup is the right copy
dup is a good choice when you need a shallow copy and you are comfortable with nested objects still being shared. That makes it useful for protecting callers from accidental mutation while keeping the copy operation cheap and predictable. It is especially handy in helper methods that need to sort, map, or otherwise reshape a value without changing the original input.
If the object has special singleton behavior or you need a deeper copy, dup may not be enough on its own. In that case, the code should say so clearly instead of pretending the copy is complete. That keeps the data flow honest and helps the next reader understand which parts are shared and which parts are independent.
understanding what gets copied
With dup, the new object starts with the same top-level state, but nested objects are not duplicated automatically. That means the method is perfect for simple wrappers, temporary modifications, and cases where the original object should stay intact while the copy changes. It is less suited to deep data structures unless the code handles the shared parts intentionally. Being clear about that boundary makes later maintenance much easier.
keeping shallow copies intentional
Because dup only makes a shallow copy, it works best when the code knows which parts of the object are safe to share. That makes it a good fit for quick adjustments to strings, arrays, and small wrapper objects. If later code will mutate nested data, the copy step should be followed by a more deliberate handling of those inner values. Clear ownership of the copy keeps the code from hiding surprises.