Memory leaks kill performance. When your GUI closes, destroy everything:

function CloseGUI()
	for _, item in pairs(screenGui:GetChildren()) do
		item:Destroy()
	end
	screenGui:Destroy()
end

Place this script anywhere inside ServerScriptService. This is where the magic of security happens.

-- Script in ServerScriptService
local remote = game.ReplicatedStorage:WaitForChild("PurchaseItem")
local itemPrices = 
    HealthPotion = 50

remote.OnServerEvent:Connect(function(player, requestedItem) -- VALIDATION LAYER (The "Better" part)

-- 1. Validate the item exists
local price = itemPrices[requestedItem]
if not price then
    warn(player.Name .. " tried to buy an invalid item!")
    return
end
-- 2. Validate the player exists and has a leaderstats folder
local leaderstats = player:FindFirstChild("leaderstats")
local coins = leaderstats and leaderstats:FindFirstChild("Coins")
if not coins or not coins.Value then
    player:Kick("Corrupted data. Rejoin.")
    return
end
-- 3. Check if they have enough money (Server check!)
if coins.Value >= price then
    -- Deduct money
    coins.Value -= price
-- Give item (Server handles inventory)
    local backpack = player:FindFirstChild("Backpack")
    local potion = game.ServerStorage.Items.HealthPotion:Clone() -- Preloaded item
    potion.Parent = backpack
-- Optional: Confirmation back to client
    local confirmRemote = game.ReplicatedStorage:WaitForChild("NotifyPlayer")
    confirmRemote:FireClient(player, "Purchased successfully!")
else
    -- Not enough gold. Tell client to revert UI.
    local failRemote = game.ReplicatedStorage:WaitForChild("NotifyFail")
    failRemote:FireClient(player, "Not enough gold!")
end

end)

Beginner scripters put everything in a single LocalScript. A superior FE strategy separates concerns: